@miosa/sdk 3.3.1 → 3.4.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/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,7 +9640,16 @@ 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;
9647
+ /**
9648
+ * Requests in flight per session. A pooled session is unref'd while idle so
9649
+ * a process that only imported the client (or finished its last request)
9650
+ * still exits; it is ref'd again for exactly as long as it carries a request.
9651
+ */
9652
+ private readonly inFlight;
9574
9653
  constructor(options?: Http2TransportOptions);
9575
9654
  prewarm(regionHostname: string, sessionsPerAddress?: number): void;
9576
9655
  addressCount(regionHostname: string): Promise<number>;
@@ -9582,7 +9661,40 @@ declare class Http2Transport implements RunnerTransport {
9582
9661
  close(): Promise<void>;
9583
9662
  private poolFor;
9584
9663
  private buildPool;
9664
+ /**
9665
+ * The addresses this pool pins to. With pinning on, every IPv4 address
9666
+ * the hostname resolves to; with it off, the hostname itself - one entry,
9667
+ * dialled as-is so the OS resolver chooses.
9668
+ */
9669
+ private resolveAddresses;
9670
+ /**
9671
+ * Opens `sessionsPerAddress` sessions against one address, in parallel.
9672
+ * sessionsPerAddress: 0 is a deliberate "open nothing" (e.g. a count-only
9673
+ * probe).
9674
+ */
9675
+ private openGroup;
9676
+ /** Closes a session slot once it connects, ignoring one that never did. */
9677
+ private closeSlot;
9678
+ /**
9679
+ * A session pinned to `address` failed to connect. Mark that address's
9680
+ * group for reopening and re-resolve in the background (single-flight),
9681
+ * so one dead address never costs every concurrent failure its own DNS
9682
+ * lookup. Never blocks or fails the request that is already on its way
9683
+ * out - the caller retries another index (C6's next-IP retry).
9684
+ */
9685
+ private noteAddressFailure;
9686
+ /**
9687
+ * Re-resolves `regionHostname` and redistributes the pool over the fresh
9688
+ * addresses: the failed address's sessions are dropped and reopened, a
9689
+ * newly advertised address gets a group of its own, an address DNS no
9690
+ * longer lists loses its sessions, and every healthy address keeps the
9691
+ * sessions it already has - so a failure on one host never costs the rest
9692
+ * their warm connections.
9693
+ */
9694
+ private rebuildPool;
9585
9695
  private connectSession;
9696
+ private retain;
9697
+ private release;
9586
9698
  private sendRequest;
9587
9699
  private sendStreamRequest;
9588
9700
  }
@@ -9611,6 +9723,12 @@ interface Http3Availability {
9611
9723
  declare function detectHttp3Support(): Http3Availability;
9612
9724
  /** Exposed for tests - clears the cached load result and availability. */
9613
9725
  declare function resetHttp3DetectionCache(): void;
9726
+ /**
9727
+ * The connection handshake, injectable so tests can observe (and
9728
+ * selectively fail) which address a connection is opened against without a
9729
+ * native QUIC listener.
9730
+ */
9731
+ type Http3Connector = (address: string, servername: string, port: number, insecure: boolean, connectTimeoutMs: number) => Promise<number>;
9614
9732
  interface Http3TransportOptions {
9615
9733
  /** Sessions opened per address when no explicit count is given. C6: 4. */
9616
9734
  defaultSessionsPerAddress?: number;
@@ -9626,6 +9744,17 @@ interface Http3TransportOptions {
9626
9744
  /** Skips certificate verification. Test-only - never set true in production. */
9627
9745
  insecure?: boolean;
9628
9746
  port?: number;
9747
+ /**
9748
+ * Pin every connection group to one resolved IPv4 address (C6), so a name
9749
+ * with several A records is dialled at all of them deliberately. Set
9750
+ * `false` to use only the hostname's first resolved address and let the
9751
+ * OS resolver pick which that is - one target, the pre-pinning shape (the
9752
+ * native addon dials a literal address, so a hostname cannot be passed
9753
+ * through here). Defaults to `true`.
9754
+ */
9755
+ pinAddresses?: boolean;
9756
+ /** Injectable for tests; defaults to the native addon's connect. */
9757
+ connect?: Http3Connector;
9629
9758
  }
9630
9759
  declare class Http3Transport implements RunnerTransport {
9631
9760
  readonly protocol: "h3";
@@ -9636,6 +9765,8 @@ declare class Http3Transport implements RunnerTransport {
9636
9765
  private readonly connectTimeoutMs;
9637
9766
  private readonly insecure;
9638
9767
  private readonly port;
9768
+ private readonly pinAddresses;
9769
+ private readonly dial;
9639
9770
  private readonly pools;
9640
9771
  constructor(options?: Http3TransportOptions);
9641
9772
  prewarm(regionHostname: string, sessionsPerAddress?: number): void;
@@ -9654,23 +9785,67 @@ declare class Http3Transport implements RunnerTransport {
9654
9785
  close(): Promise<void>;
9655
9786
  private poolFor;
9656
9787
  private buildPool;
9788
+ /**
9789
+ * The addresses this pool pins to. With pinning on, every IPv4 address
9790
+ * the hostname resolves to. With it off, the hostname is resolved once
9791
+ * and only its first address is kept - one target, chosen by the OS
9792
+ * resolver, which is the closest this transport can get to HTTP/2's
9793
+ * hostname dial: the native addon takes a literal socket address, never
9794
+ * a hostname.
9795
+ */
9796
+ private resolveAddresses;
9797
+ /** Opens `sessionsPerAddress` connections against one address, in parallel. */
9798
+ private openGroup;
9799
+ /** Closes a connection slot once it connects, ignoring one that never did. */
9800
+ private closeSlot;
9801
+ /**
9802
+ * A connection pinned to `address` failed to connect. Mark that address's
9803
+ * group for reopening and re-resolve in the background (single-flight),
9804
+ * so one dead address never costs every concurrent failure its own DNS
9805
+ * lookup. Never blocks or fails the request already on its way out.
9806
+ */
9807
+ private noteAddressFailure;
9808
+ /**
9809
+ * Re-resolves `regionHostname` and redistributes the pool over the fresh
9810
+ * addresses: the failed address's connections are dropped and reopened, a
9811
+ * newly advertised address gets a group of its own, an address DNS no
9812
+ * longer lists loses its connections, and every healthy address keeps the
9813
+ * connections it already has.
9814
+ */
9815
+ private rebuildPool;
9657
9816
  private connectSession;
9658
9817
  private sendRequest;
9659
9818
  }
9660
9819
 
9661
9820
  /**
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.
9821
+ * Picks the transport for a region host.
9822
+ *
9823
+ * HTTP/2 unless the caller opts into racing HTTP/3 against it. A platform
9824
+ * with no native HTTP/3 addon is always HTTP/2 with zero QUIC probing
9825
+ * either way - see http3-transport.ts.
9826
+ *
9827
+ * Why HTTP/2 is the default rather than the race: on a cold burst the race
9828
+ * costs several times what HTTP/2 does. The promotion to HTTP/3 is decided
9829
+ * by address 0's *connection* being ready, while every request for that host
9830
+ * is then spread over all of its sessions, so a burst is dispatched onto
9831
+ * HTTP/3 sessions that have not carried a request yet. Measured on one region
9832
+ * host at c=100, alternating order, three runs each: TTI p50 203 / 221 / 267 ms
9833
+ * on the race against 51 / 53 / 50 ms on HTTP/2 at 4 sessions per address, and
9834
+ * 28 / 55 / 235 / 250 ms against 43 / 57 / 52 / 66 ms at the default 8. Once the
9835
+ * pool is genuinely warm HTTP/3 is the faster of the two, so it stays available
9836
+ * for callers who can pre-warm it; it is just not a safe default for the first
9837
+ * burst, and a first burst is what a latency benchmark measures.
9669
9838
  */
9670
9839
 
9671
9840
  interface BestTransportOptions {
9672
9841
  http2?: Http2TransportOptions;
9673
9842
  http3?: Http3TransportOptions;
9843
+ /**
9844
+ * Race HTTP/3 against HTTP/2 per region host. Default false, for the reason
9845
+ * in this module's doc comment. HTTP/3 still only ever promotes a host for
9846
+ * *future* requests, so opting in never delays a request in flight.
9847
+ */
9848
+ raceHttp3?: boolean | undefined;
9674
9849
  }
9675
9850
  interface BestTransportResult {
9676
9851
  transport: RunnerTransport;
@@ -9766,6 +9941,14 @@ declare class RunnerError extends Error {
9766
9941
  * two carries it.
9767
9942
  */
9768
9943
  get isBusy(): boolean;
9944
+ /**
9945
+ * `429 rate_limited`: the key's own window on this runner has no room.
9946
+ *
9947
+ * Separate from `isBusy`, which means this runner's admission is full for
9948
+ * everyone. The limiter counts per key per runner, so a refusal here says
9949
+ * nothing about the next address, and `createSandbox` may try that one.
9950
+ */
9951
+ get isRateLimited(): boolean;
9769
9952
  /** C2: the runner doesn't serve this create shape at all - send it to api.miosa.ai instead. */
9770
9953
  get isUnsupportedRequest(): boolean;
9771
9954
  }