UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

184 lines • 6.82 kB
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= import { sleep } from "./abort.js"; import { createAsyncContext } from "./async_context.js"; /** Platform clock used when no clock is supplied. */ export class SystemClock { /** Marks a platform clock, so two copies of this module still agree. */ platform = true; /** Seconds since the unix epoch. */ now() { return Date.now() / 1000; } /** Seconds from an arbitrary origin that never moves backwards. */ monotonic() { return performance.now() / 1000; } /** Wait `seconds` of real time; zero or less returns at once. */ sleep(seconds) { return sleep(seconds * 1000); } } /** * Clock advanced explicitly by the caller. Readings have no side effects. * A sleep advances the clock by its length and returns without waiting, so * a wait costs no real time. Waits do not overlap: two tasks that each sleep * 30 seconds advance it by 60. */ export class ManualClock { wall; mono = 0; /** @param start initial wall time in seconds; monotonic starts at zero. */ constructor(start = 0) { this.wall = start; } /** Seconds on the virtual wall clock. */ now() { return this.wall; } /** Seconds on the virtual monotonic clock. */ monotonic() { return this.mono; } /** * Move the clock forward by `seconds`. * * @param seconds how far forward to move; must not be negative, since * a monotonic reading may not go back. */ advance(seconds) { if (seconds < 0) throw new RangeError('cannot advance a clock backwards'); this.wall += seconds; this.mono += seconds; } /** * Advance by `seconds` and yield once. Zero, a negative or NaN advances * nothing. */ async sleep(seconds) { if (seconds > 0) this.advance(seconds); await Promise.resolve(); } } /** Whether `clock` reads the platform clock, so any two such agree. */ export function isSystemClock(clock) { return clock.platform === true; } /** * `base` shifted by a fixed offset: it starts at the instant it is built for * and ticks at `base`'s rate, as a workspace's `system_time` does. Monotonic * time and waits are `base`'s. Mirrors Python's `ShiftedClock`. */ export class ShiftedClock { base; offset; /** * @param base the clock it ticks with. * @param offset seconds added to `base`'s wall time. */ constructor(base, offset) { this.base = base; this.offset = offset; } /** `base` shifted so that it reads `start` seconds now. */ static starting(base, start) { return new ShiftedClock(base, start - base.now()); } /** Seconds since the unix epoch, shifted. */ now() { return this.base.now() + this.offset; } /** `base`'s monotonic seconds. */ monotonic() { return this.base.monotonic(); } /** Wait `seconds` on `base`. */ sleep(seconds) { return this.base.sleep(seconds); } } // An ISO 8601 instant with an offset: `2026-03-02T09:00:00Z`, `+08:00`. const INSTANT = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d+))?)?(Z|([+-])(\d{2}):([0-5]\d))$/; // 2262-01-01T00:00:00Z, the first second a guest's nanosecond clock cannot // hold. const SYSTEM_TIME_END = 9214646400; /** * Seconds since the unix epoch a `system_time` names: an ISO 8601 instant * with an offset (`Z` or `+08:00`), from 1970 through 2261, the years a * guest's nanosecond clock holds. Mirrors Python's `parse_system_time`. * * @throws Error when it is not one. */ export function parseSystemTime(text) { // Each field in its range, as Python's `datetime.fromisoformat` holds it // (Date.parse lets `02-30` and `24:00` roll over), and milliseconds // kept, as Date's are. const m = INSTANT.exec(text); let seconds = NaN; if (m !== null) { // An unmatched group (no seconds) is undefined. const fields = m.slice(1, 7); const [year, month, day, hour, minute, second] = fields.map((f) => Number(f ?? 0)); const ms = Number(((m[7] ?? '') + '000').slice(0, 3)); const offset = m[8] === 'Z' ? 0 : (m[9] === '-' ? -1 : 1) * (Number(m[10]) * 60 + Number(m[11])); const at = Date.UTC(year ?? 0, (month ?? 1) - 1, day, hour, minute, second, ms); const back = new Date(at); if (back.getUTCFullYear() === year && back.getUTCMonth() + 1 === month && back.getUTCDate() === day && back.getUTCHours() === hour && back.getUTCMinutes() === minute && back.getUTCSeconds() === second && Math.abs(offset) < 24 * 60) { seconds = (at - offset * 60_000) / 1000; } } if (!(seconds >= 0 && seconds < SYSTEM_TIME_END)) { throw new Error(`system_time ${JSON.stringify(text)} is not an ISO 8601 instant with an offset from 1970 through 2261, as 2026-03-02T09:00:00Z`); } return seconds; } // The system clock of the workspace a line or an op runs in: what `date`, // file times and the runtimes read. Null, or unbound, is the host's time. const systemClock = createAsyncContext(); /** * `fn` run on a workspace's system clock, which `systemNow` reads. Mirrors * Python's `run_with_system_clock`. */ export function runWithSystemClock(clock, fn) { return Promise.resolve(systemClock.run(clock, fn)); } /** * The running workspace's system clock, null for the host's. A browser * cannot tell one task's binding from another's, so there it is the newest * live one. Mirrors Python's `bound_system_clock`. */ export function boundSystemClock() { return systemClock.getStore() ?? null; } /** * Seconds since the unix epoch on the running workspace's system clock: what * the agent sees as now. Mirrors Python's `system_now`. */ export function systemNow() { const clock = boundSystemClock(); return clock === null ? Date.now() / 1000 : clock.now(); } /** `systemNow` as a `Date`. */ export function systemDate() { return new Date(systemNow() * 1000); } //# sourceMappingURL=clock.js.map