@miosa/sdk 3.3.0 → 3.4.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.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as ws from 'ws';
2
2
  import EventEmitter from 'node:events';
3
+ import * as http2 from 'node:http2';
3
4
 
4
5
  interface RequestOptions {
5
6
  method?: string;
@@ -2243,6 +2244,13 @@ interface MiosaClientConfig {
2243
2244
  region?: string;
2244
2245
  /** Overrides `miosa.ai` - for self-hosted / test deployments. */
2245
2246
  baseDomain?: string;
2247
+ /**
2248
+ * Pin each runner session group to one resolved IPv4 address, so
2249
+ * `run-<region>.miosa.ai`'s several A records are all used. Set `false`
2250
+ * to dial the hostname and let the OS resolver choose instead.
2251
+ * Defaults to `true`.
2252
+ */
2253
+ pinAddresses?: boolean;
2246
2254
  };
2247
2255
  }
2248
2256
 
@@ -8855,8 +8863,22 @@ interface RunnerClientOptions {
8855
8863
  tenant?: string;
8856
8864
  /** Sessions opened per resolved region address. C6 default: 8. */
8857
8865
  sessionsPerAddress?: number;
8866
+ /**
8867
+ * Pin each session group to one resolved runner address (C6), so creates
8868
+ * spread over every host behind `run-<region>.miosa.ai`. Set `false` to
8869
+ * dial the hostname and let the OS resolver choose instead. Defaults to
8870
+ * `true`; only consulted when `transport` is not injected.
8871
+ */
8872
+ pinAddresses?: boolean;
8858
8873
  /** Injectable for tests; defaults to the best available transport. */
8859
8874
  transport?: RunnerTransport;
8875
+ /**
8876
+ * Race HTTP/3 against HTTP/2 per region host. Default false: HTTP/2 only.
8877
+ * See transport-factory.ts for why the race is opt-in - on a cold burst the
8878
+ * promotion can land requests on HTTP/3 sessions that are not yet carrying
8879
+ * traffic, which costs several times what HTTP/2 does.
8880
+ */
8881
+ http3?: boolean;
8860
8882
  /**
8861
8883
  * Called for a create the runner doesn't serve (C2: template, image,
8862
8884
  * snapshot, cwd, persistent, non-xs shapes) - wired by `Miosa` to
@@ -9003,6 +9025,13 @@ declare class RunnerClient {
9003
9025
  private readonly addressIndexByTag;
9004
9026
  /** sandbox id -> tag, set only once a 421 corrects the id-derived guess. */
9005
9027
  private readonly correctedTag;
9028
+ /**
9029
+ * Rotating start address for creates, so a cold burst spreads over every
9030
+ * resolved runner address instead of all of it landing on address 0.
9031
+ * Advanced once per create; the retry on `429 runtime_busy`/connect
9032
+ * failure still walks the other addresses from there.
9033
+ */
9034
+ private createCursor;
9006
9035
  constructor(options: RunnerClientOptions);
9007
9036
  get protocol(): RunnerProtocol;
9008
9037
  /** The tag a 421 has corrected sandboxId to, if any. */
@@ -9510,17 +9539,32 @@ declare function sandboxTag(sandboxId: string): string;
9510
9539
  /** Resolves a hostname to the list of IP addresses it should be reached at. */
9511
9540
  interface AddressResolver {
9512
9541
  resolve(hostname: string): Promise<string[]>;
9542
+ /**
9543
+ * Forgets anything cached for `hostname`, so the next `resolve` asks the
9544
+ * OS resolver again. The transports call this after a per-address connect
9545
+ * failure: a runner host that has moved, or whose address has gone away,
9546
+ * is re-resolved rather than retried at a stale address for the rest of
9547
+ * the cache window. Optional - a resolver with nothing to cache omits it.
9548
+ */
9549
+ invalidate?(hostname: string): void;
9513
9550
  }
9551
+ /** Resolves a hostname straight to its list of IPv4 addresses, in resolver order. */
9552
+ type AddressLookup = (hostname: string) => Promise<string[]>;
9514
9553
  /**
9515
- * Default resolver: plain DNS A-record lookup, with IP literals and
9516
- * `localhost` passed through unchanged so tests and self-hosted runners
9517
- * can point the transport straight at a fixed address without a resolver.
9554
+ * Default resolver: every IPv4 address of the hostname, cached for a short
9555
+ * TTL (60 s - the runners' advertised A-record TTL) so a burst of requests
9556
+ * resolves once instead of hitting the resolver per request. IP literals
9557
+ * and `localhost` are passed through unchanged so tests and self-hosted
9558
+ * runners can point the transport straight at a fixed address without a
9559
+ * resolver.
9518
9560
  */
9519
9561
  declare class DnsAddressResolver implements AddressResolver {
9520
9562
  private readonly ttlMs;
9563
+ private readonly lookupAddresses;
9521
9564
  private readonly cache;
9522
- constructor(ttlMs?: number);
9565
+ constructor(ttlMs?: number, lookupAddresses?: AddressLookup);
9523
9566
  resolve(hostname: string): Promise<string[]>;
9567
+ invalidate(hostname: string): void;
9524
9568
  }
9525
9569
  /** A fixed-table resolver, for tests and for pinning to known runner IPs (C1). */
9526
9570
  declare class StaticAddressResolver implements AddressResolver {
@@ -9551,8 +9595,23 @@ declare class StaticAddressResolver implements AddressResolver {
9551
9595
  * coalescing: the cert covers `run-<region>.miosa.ai` and
9552
9596
  * `*.run-<region>.miosa.ai`, and a per-host name's one A record is the
9553
9597
  * same IP already in this pool - see transport.ts's doc comment).
9598
+ *
9599
+ * Pinning is what makes the dial target explicit: the hostname is resolved
9600
+ * once, and every session dials its own resolved IP while SNI and the
9601
+ * HTTP/2 `:authority` stay the hostname, so a name with several A records
9602
+ * spreads over all of them deterministically instead of wherever the OS
9603
+ * resolver happens to point. `pinAddresses: false` turns this off and dials
9604
+ * the hostname itself, letting the OS resolver choose (one address, the
9605
+ * pre-pinning behaviour). A per-address connect failure drops that
9606
+ * address's sessions, invalidates the resolver's cache and re-resolves, so
9607
+ * a host that has moved is not retried at a stale address.
9554
9608
  */
9555
9609
 
9610
+ /**
9611
+ * The dial itself, injectable so tests can observe (and selectively fail)
9612
+ * which address a session is opened against without a real socket.
9613
+ */
9614
+ type Http2Connector = (authority: string, options: http2.SecureClientSessionOptions) => http2.ClientHttp2Session;
9556
9615
  interface Http2TransportOptions {
9557
9616
  /** Sessions opened per address when no explicit count is given. C6 default: 8. */
9558
9617
  defaultSessionsPerAddress?: number;
@@ -9562,6 +9621,17 @@ interface Http2TransportOptions {
9562
9621
  resolver?: AddressResolver;
9563
9622
  /** Default per-request timeout when the caller doesn't set one. */
9564
9623
  defaultTimeoutMs?: number;
9624
+ /**
9625
+ * Pin every session group to one resolved IPv4 address (C6), so a name
9626
+ * with several A records is dialled at all of them deliberately. Set
9627
+ * `false` to dial the hostname itself and let the OS resolver pick - the
9628
+ * pre-pinning behaviour, one address only. Defaults to `true`.
9629
+ */
9630
+ pinAddresses?: boolean;
9631
+ /** Skips TLS certificate verification. Test-only - never set true in production. */
9632
+ insecure?: boolean;
9633
+ /** Injectable for tests; defaults to `node:http2`'s connect. */
9634
+ connect?: Http2Connector;
9565
9635
  }
9566
9636
  declare class Http2Transport implements RunnerTransport {
9567
9637
  readonly protocol: "h2";
@@ -9570,6 +9640,9 @@ declare class Http2Transport implements RunnerTransport {
9570
9640
  private readonly port;
9571
9641
  private readonly resolver;
9572
9642
  private readonly defaultTimeoutMs;
9643
+ private readonly pinAddresses;
9644
+ private readonly insecure;
9645
+ private readonly dial;
9573
9646
  private readonly pools;
9574
9647
  constructor(options?: Http2TransportOptions);
9575
9648
  prewarm(regionHostname: string, sessionsPerAddress?: number): void;
@@ -9582,6 +9655,37 @@ declare class Http2Transport implements RunnerTransport {
9582
9655
  close(): Promise<void>;
9583
9656
  private poolFor;
9584
9657
  private buildPool;
9658
+ /**
9659
+ * The addresses this pool pins to. With pinning on, every IPv4 address
9660
+ * the hostname resolves to; with it off, the hostname itself - one entry,
9661
+ * dialled as-is so the OS resolver chooses.
9662
+ */
9663
+ private resolveAddresses;
9664
+ /**
9665
+ * Opens `sessionsPerAddress` sessions against one address, in parallel.
9666
+ * sessionsPerAddress: 0 is a deliberate "open nothing" (e.g. a count-only
9667
+ * probe).
9668
+ */
9669
+ private openGroup;
9670
+ /** Closes a session slot once it connects, ignoring one that never did. */
9671
+ private closeSlot;
9672
+ /**
9673
+ * A session pinned to `address` failed to connect. Mark that address's
9674
+ * group for reopening and re-resolve in the background (single-flight),
9675
+ * so one dead address never costs every concurrent failure its own DNS
9676
+ * lookup. Never blocks or fails the request that is already on its way
9677
+ * out - the caller retries another index (C6's next-IP retry).
9678
+ */
9679
+ private noteAddressFailure;
9680
+ /**
9681
+ * Re-resolves `regionHostname` and redistributes the pool over the fresh
9682
+ * addresses: the failed address's sessions are dropped and reopened, a
9683
+ * newly advertised address gets a group of its own, an address DNS no
9684
+ * longer lists loses its sessions, and every healthy address keeps the
9685
+ * sessions it already has - so a failure on one host never costs the rest
9686
+ * their warm connections.
9687
+ */
9688
+ private rebuildPool;
9585
9689
  private connectSession;
9586
9690
  private sendRequest;
9587
9691
  private sendStreamRequest;
@@ -9589,7 +9693,7 @@ declare class Http2Transport implements RunnerTransport {
9589
9693
 
9590
9694
  /**
9591
9695
  * HTTP/3 runner transport: a napi-rs addon over quinn + h3
9592
- * (native/h3-runner/), mirroring isorun's own native QUIC client.
9696
+ * (native/h3-runner/), so the runner lane can use QUIC where it is available.
9593
9697
  *
9594
9698
  * Built today for darwin-arm64 only - see native/h3-runner/README.md for
9595
9699
  * exactly which platforms still need a CI build before this ships
@@ -9611,6 +9715,12 @@ interface Http3Availability {
9611
9715
  declare function detectHttp3Support(): Http3Availability;
9612
9716
  /** Exposed for tests - clears the cached load result and availability. */
9613
9717
  declare function resetHttp3DetectionCache(): void;
9718
+ /**
9719
+ * The connection handshake, injectable so tests can observe (and
9720
+ * selectively fail) which address a connection is opened against without a
9721
+ * native QUIC listener.
9722
+ */
9723
+ type Http3Connector = (address: string, servername: string, port: number, insecure: boolean, connectTimeoutMs: number) => Promise<number>;
9614
9724
  interface Http3TransportOptions {
9615
9725
  /** Sessions opened per address when no explicit count is given. C6: 4. */
9616
9726
  defaultSessionsPerAddress?: number;
@@ -9626,6 +9736,17 @@ interface Http3TransportOptions {
9626
9736
  /** Skips certificate verification. Test-only - never set true in production. */
9627
9737
  insecure?: boolean;
9628
9738
  port?: number;
9739
+ /**
9740
+ * Pin every connection group to one resolved IPv4 address (C6), so a name
9741
+ * with several A records is dialled at all of them deliberately. Set
9742
+ * `false` to use only the hostname's first resolved address and let the
9743
+ * OS resolver pick which that is - one target, the pre-pinning shape (the
9744
+ * native addon dials a literal address, so a hostname cannot be passed
9745
+ * through here). Defaults to `true`.
9746
+ */
9747
+ pinAddresses?: boolean;
9748
+ /** Injectable for tests; defaults to the native addon's connect. */
9749
+ connect?: Http3Connector;
9629
9750
  }
9630
9751
  declare class Http3Transport implements RunnerTransport {
9631
9752
  readonly protocol: "h3";
@@ -9636,6 +9757,8 @@ declare class Http3Transport implements RunnerTransport {
9636
9757
  private readonly connectTimeoutMs;
9637
9758
  private readonly insecure;
9638
9759
  private readonly port;
9760
+ private readonly pinAddresses;
9761
+ private readonly dial;
9639
9762
  private readonly pools;
9640
9763
  constructor(options?: Http3TransportOptions);
9641
9764
  prewarm(regionHostname: string, sessionsPerAddress?: number): void;
@@ -9654,23 +9777,67 @@ declare class Http3Transport implements RunnerTransport {
9654
9777
  close(): Promise<void>;
9655
9778
  private poolFor;
9656
9779
  private buildPool;
9780
+ /**
9781
+ * The addresses this pool pins to. With pinning on, every IPv4 address
9782
+ * the hostname resolves to. With it off, the hostname is resolved once
9783
+ * and only its first address is kept - one target, chosen by the OS
9784
+ * resolver, which is the closest this transport can get to HTTP/2's
9785
+ * hostname dial: the native addon takes a literal socket address, never
9786
+ * a hostname.
9787
+ */
9788
+ private resolveAddresses;
9789
+ /** Opens `sessionsPerAddress` connections against one address, in parallel. */
9790
+ private openGroup;
9791
+ /** Closes a connection slot once it connects, ignoring one that never did. */
9792
+ private closeSlot;
9793
+ /**
9794
+ * A connection pinned to `address` failed to connect. Mark that address's
9795
+ * group for reopening and re-resolve in the background (single-flight),
9796
+ * so one dead address never costs every concurrent failure its own DNS
9797
+ * lookup. Never blocks or fails the request already on its way out.
9798
+ */
9799
+ private noteAddressFailure;
9800
+ /**
9801
+ * Re-resolves `regionHostname` and redistributes the pool over the fresh
9802
+ * addresses: the failed address's connections are dropped and reopened, a
9803
+ * newly advertised address gets a group of its own, an address DNS no
9804
+ * longer lists loses its connections, and every healthy address keeps the
9805
+ * connections it already has.
9806
+ */
9807
+ private rebuildPool;
9657
9808
  private connectSession;
9658
9809
  private sendRequest;
9659
9810
  }
9660
9811
 
9661
9812
  /**
9662
- * Picks the best transport available, and - when HTTP/3 is available -
9663
- * runs it against HTTP/2 per region host so a blocked or slow QUIC
9664
- * handshake never costs a request time (RUNNER-CONTRACTS-2026-10-02.md,
9665
- * C6; see http3-transport.ts's doc comment for the full requirement).
9666
- *
9667
- * Today, on a platform with no native HTTP/3 addon, this is unconditional
9668
- * HTTP/2 with zero QUIC probing - see http3-transport.ts.
9813
+ * Picks the transport for a region host.
9814
+ *
9815
+ * HTTP/2 unless the caller opts into racing HTTP/3 against it. A platform
9816
+ * with no native HTTP/3 addon is always HTTP/2 with zero QUIC probing
9817
+ * either way - see http3-transport.ts.
9818
+ *
9819
+ * Why HTTP/2 is the default rather than the race: on a cold burst the race
9820
+ * costs several times what HTTP/2 does. The promotion to HTTP/3 is decided
9821
+ * by address 0's *connection* being ready, while every request for that host
9822
+ * is then spread over all of its sessions, so a burst is dispatched onto
9823
+ * HTTP/3 sessions that have not carried a request yet. Measured on one region
9824
+ * host at c=100, alternating order, three runs each: TTI p50 203 / 221 / 267 ms
9825
+ * on the race against 51 / 53 / 50 ms on HTTP/2 at 4 sessions per address, and
9826
+ * 28 / 55 / 235 / 250 ms against 43 / 57 / 52 / 66 ms at the default 8. Once the
9827
+ * pool is genuinely warm HTTP/3 is the faster of the two, so it stays available
9828
+ * for callers who can pre-warm it; it is just not a safe default for the first
9829
+ * burst, and a first burst is what a latency benchmark measures.
9669
9830
  */
9670
9831
 
9671
9832
  interface BestTransportOptions {
9672
9833
  http2?: Http2TransportOptions;
9673
9834
  http3?: Http3TransportOptions;
9835
+ /**
9836
+ * Race HTTP/3 against HTTP/2 per region host. Default false, for the reason
9837
+ * in this module's doc comment. HTTP/3 still only ever promotes a host for
9838
+ * *future* requests, so opting in never delays a request in flight.
9839
+ */
9840
+ raceHttp3?: boolean | undefined;
9674
9841
  }
9675
9842
  interface BestTransportResult {
9676
9843
  transport: RunnerTransport;
@@ -9766,6 +9933,14 @@ declare class RunnerError extends Error {
9766
9933
  * two carries it.
9767
9934
  */
9768
9935
  get isBusy(): boolean;
9936
+ /**
9937
+ * `429 rate_limited`: the key's own window on this runner has no room.
9938
+ *
9939
+ * Separate from `isBusy`, which means this runner's admission is full for
9940
+ * everyone. The limiter counts per key per runner, so a refusal here says
9941
+ * nothing about the next address, and `createSandbox` may try that one.
9942
+ */
9943
+ get isRateLimited(): boolean;
9769
9944
  /** C2: the runner doesn't serve this create shape at all - send it to api.miosa.ai instead. */
9770
9945
  get isUnsupportedRequest(): boolean;
9771
9946
  }