@tangleai/models 0.21.1 → 0.25.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/src/replay.d.ts CHANGED
@@ -1,30 +1,59 @@
1
1
  /**
2
- * The replay cache seam a host implements.
3
- * @typedef {Object} ReplayCache
4
- * @property {(key: string) => any} get - the stored value, or `undefined`
5
- * for a miss; may answer a promise
6
- * @property {(key: string, value: any) => any} set - remember a value
7
- * under a key; may answer a promise. The value is JSON-only.
2
+ * The replay seam behind both wire clients: what a remembered reply is
3
+ * keyed by, and what a remembered entry must look like before it is
4
+ * served. This module owns the two decisions a host must not be left
5
+ * to make twice
6
+ *
7
+ * - **the key is the effective wire request, canonicalized.** It is
8
+ * built by the client after endpoint resolution and default
9
+ * application, from `{ wire, provider, base, request }` where
10
+ * `request` is the body the client would POST (minus `stream`, which
11
+ * does not change an answer). Never the headers (they carry the
12
+ * credential), never the signal, never a callback. Because the key
13
+ * is the body rather than an allow-list of request members, an
14
+ * option added to the body later enters the key by construction — it
15
+ * cannot alias an older key. The serialization is `semanticKey`:
16
+ * injective over plain data, so two requests share a key exactly when
17
+ * they are the same request, and a request that cannot be keyed
18
+ * injectively (a function inside `tools`, a cycle) is refused up
19
+ * front rather than folded onto someone else's entry;
20
+ *
21
+ * - **a stored entry is verified, not trusted.** A chat entry must carry
22
+ * a normalized result and the wall time of the purchase; an embedding
23
+ * entry must carry a vector of finite numbers at the settled width.
24
+ * Anything else is `AI0003`, never served — a cache that answers
25
+ * garbage is worse than a cache that is down.
26
+ *
27
+ * The client hands the adapter the complete canonical string. It is
28
+ * long (a whole conversation is in it) and that is the point: it is
29
+ * collision-free. An adapter that needs a fixed-width storage id hashes
30
+ * it — with a cryptographic hash, because a 32-bit hash over prompts
31
+ * that differ by one token would sooner or later serve one prompt's
32
+ * answer for another's.
33
+ *
34
+ * The adapter contract is two members, each sync or async:
35
+ * `get(key) → value | undefined` and `set(key, value)`. The seam FAILS
36
+ * CLOSED: an adapter that throws fails the call. An adapter that wants
37
+ * to fail open — keep buying while its storage is broken — catches its
38
+ * own errors and answers `undefined`; the client will not guess which
39
+ * it wanted.
8
40
  */
41
+ /** The replay cache seam a host implements. */
9
42
  /**
10
43
  * The `cache` option, checked once at client construction: absent means
11
44
  * no cache; present means both members are functions, or `AI0001`.
12
- * @param {unknown} cache
13
- * @returns {ReplayCache | null}
14
45
  */
15
- export function normalizeCache(cache: unknown): ReplayCache | null;
46
+ export declare function normalizeCache(cache: unknown): ReplayCache | null;
16
47
  /**
17
48
  * The key one request has under one endpoint: the canonical
18
49
  * serialization of the wire, the credential-free endpoint identity and
19
50
  * the effective request. The same request keys the same string on
20
51
  * every host.
21
- * @param {'chat' | 'embeddings'} wire
22
- * @param {{ provider: string, base: string }} endpoint
23
- * @param {any} request - the body the client would POST, `stream` removed
24
- * @returns {string}
52
+ * @param request - the body the client would POST, `stream` removed
53
+ * @returns
25
54
  * @throws {AiError} `AI0001` when the request cannot be keyed injectively
26
55
  */
27
- export function replayKey(wire: "chat" | "embeddings", endpoint: {
56
+ export declare function replayKey(wire: 'chat' | 'embeddings', endpoint: {
28
57
  provider: string;
29
58
  base: string;
30
59
  }, request: any): string;
@@ -32,10 +61,8 @@ export function replayKey(wire: "chat" | "embeddings", endpoint: {
32
61
  * A stored chat entry, verified: `{ value, ms }` with `value` a
33
62
  * normalized result carrying a `message` object and `ms` a finite
34
63
  * number — or `AI0003`.
35
- * @param {any} entry
36
- * @returns {{ value: any, ms: number }}
37
64
  */
38
- export function verifyChatEntry(entry: any): {
65
+ export declare function verifyChatEntry(entry: any): {
39
66
  value: any;
40
67
  ms: number;
41
68
  };
@@ -44,33 +71,21 @@ export function verifyChatEntry(entry: any): {
44
71
  * ms }` with `vector` a non-empty array of finite numbers at the settled
45
72
  * width (any positive width when none is settled yet — the first replay
46
73
  * settles it exactly as a first wire reply would) — or `AI0003`.
47
- * @param {any} entry
48
- * @param {number | undefined} dims - the settled width, if any
49
- * @returns {Float32Array}
74
+ * @param dims - the settled width, if any
50
75
  */
51
- export function verifyEmbeddingEntry(entry: any, dims: number | undefined): Float32Array;
76
+ export declare function verifyEmbeddingEntry(entry: any, dims: number | undefined): Float32Array;
52
77
  /**
53
78
  * A JSON-only copy: what is stored, and what a replay answers, so that a
54
79
  * caller mutating its result never mutates the adapter's entry.
55
80
  * @template T
56
- * @param {T} value
57
- * @returns {T}
58
81
  */
59
- export function cloneJson<T>(value: T): T;
82
+ export declare function cloneJson<T>(value: T): T;
60
83
  /** Milliseconds now, for the wall time of a purchase. */
61
- export function now(): number;
62
- /**
63
- * The replay cache seam a host implements.
64
- */
84
+ export declare function now(): number;
65
85
  export type ReplayCache = {
66
- /**
67
- * - the stored value, or `undefined`
68
- * for a miss; may answer a promise
69
- */
70
- get: (key: string) => any;
71
- /**
72
- * - remember a value
73
- * under a key; may answer a promise. The value is JSON-only.
74
- */
86
+ get: (key: string) => any; /**
87
+ * - remember a value
88
+ * under a key; may answer a promise. The value is JSON-only.
89
+ */
75
90
  set: (key: string, value: any) => any;
76
91
  };
package/src/replay.js CHANGED
@@ -1,4 +1,3 @@
1
- //@ts-check
2
1
  /**
3
2
  * The replay seam behind both wire clients: what a remembered reply is
4
3
  * keyed by, and what a remembered entry must look like before it is
@@ -39,103 +38,76 @@
39
38
  * own errors and answers `undefined`; the client will not guess which
40
39
  * it wanted.
41
40
  */
42
-
43
41
  import { semanticKey } from '@jarenjs/core/object';
44
-
45
- import { AiError } from './errors.js';
46
- import { verifyEmbeddingComponents } from './embedding-vector.js';
47
-
48
- /**
49
- * The replay cache seam a host implements.
50
- * @typedef {Object} ReplayCache
51
- * @property {(key: string) => any} get - the stored value, or `undefined`
52
- * for a miss; may answer a promise
53
- * @property {(key: string, value: any) => any} set - remember a value
54
- * under a key; may answer a promise. The value is JSON-only.
55
- */
56
-
42
+ import { AiError } from "./errors.js";
43
+ import { verifyEmbeddingComponents } from "./embedding-vector.js";
44
+ /** The replay cache seam a host implements. */
57
45
  /**
58
46
  * The `cache` option, checked once at client construction: absent means
59
47
  * no cache; present means both members are functions, or `AI0001`.
60
- * @param {unknown} cache
61
- * @returns {ReplayCache | null}
62
48
  */
63
49
  export function normalizeCache(cache) {
64
- if (cache === undefined || cache === null) return null;
65
- const candidate = /** @type {any} */ (cache);
66
- if (typeof candidate.get !== 'function' || typeof candidate.set !== 'function')
67
- throw new AiError('AI0001', 'cache needs { get(key), set(key, value) } both functions, sync or async');
68
- return candidate;
50
+ if (cache === undefined || cache === null)
51
+ return null;
52
+ const candidate = cache;
53
+ if (typeof candidate.get !== 'function' || typeof candidate.set !== 'function')
54
+ throw new AiError('AI0001', 'cache needs { get(key), set(key, value) } — both functions, sync or async');
55
+ return candidate;
69
56
  }
70
-
71
57
  /**
72
58
  * The key one request has under one endpoint: the canonical
73
59
  * serialization of the wire, the credential-free endpoint identity and
74
60
  * the effective request. The same request keys the same string on
75
61
  * every host.
76
- * @param {'chat' | 'embeddings'} wire
77
- * @param {{ provider: string, base: string }} endpoint
78
- * @param {any} request - the body the client would POST, `stream` removed
79
- * @returns {string}
62
+ * @param request - the body the client would POST, `stream` removed
63
+ * @returns
80
64
  * @throws {AiError} `AI0001` when the request cannot be keyed injectively
81
65
  */
82
66
  export function replayKey(wire, endpoint, request) {
83
- try {
84
- return semanticKey({ wire, provider: endpoint.provider, base: endpoint.base, request });
85
- }
86
- catch (err) {
87
- throw new AiError('AI0001',
88
- `the ${wire} request is not cacheable: ${/** @type {Error} */ (err).message}`);
89
- }
67
+ try {
68
+ return semanticKey({ wire, provider: endpoint.provider, base: endpoint.base, request });
69
+ }
70
+ catch (err) {
71
+ throw new AiError('AI0001', `the ${wire} request is not cacheable: ${err.message}`);
72
+ }
90
73
  }
91
-
92
- /** @param {any} value */
74
+ /** @param value */
93
75
  const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
94
-
95
76
  /**
96
77
  * A stored chat entry, verified: `{ value, ms }` with `value` a
97
78
  * normalized result carrying a `message` object and `ms` a finite
98
79
  * number — or `AI0003`.
99
- * @param {any} entry
100
- * @returns {{ value: any, ms: number }}
101
80
  */
102
81
  export function verifyChatEntry(entry) {
103
- if (!isRecord(entry) || !isRecord(entry.value) || !isRecord(entry.value.message)
104
- || typeof entry.ms !== 'number' || !Number.isFinite(entry.ms))
105
- throw new AiError('AI0003', 'malformed replay entry for the chat wire: expected { value: { message, … }, ms }');
106
- return entry;
82
+ if (!isRecord(entry) || !isRecord(entry.value) || !isRecord(entry.value.message)
83
+ || typeof entry.ms !== 'number' || !Number.isFinite(entry.ms))
84
+ throw new AiError('AI0003', 'malformed replay entry for the chat wire: expected { value: { message, … }, ms }');
85
+ return entry;
107
86
  }
108
-
109
87
  /**
110
88
  * A stored embedding entry, verified into a fresh vector: `{ vector,
111
89
  * ms }` with `vector` a non-empty array of finite numbers at the settled
112
90
  * width (any positive width when none is settled yet — the first replay
113
91
  * settles it exactly as a first wire reply would) — or `AI0003`.
114
- * @param {any} entry
115
- * @param {number | undefined} dims - the settled width, if any
116
- * @returns {Float32Array}
92
+ * @param dims - the settled width, if any
117
93
  */
118
94
  export function verifyEmbeddingEntry(entry, dims) {
119
- const vector = isRecord(entry) ? entry.vector : undefined;
120
- if (!Array.isArray(vector) || vector.length === 0)
121
- throw new AiError('AI0003', 'malformed replay entry for the embeddings wire: expected { vector: number[], ms }');
122
- if (dims !== undefined && vector.length !== dims)
123
- throw new AiError('AI0003', `replay entry carries ${vector.length} dimensions, expected ${dims}`);
124
- return verifyEmbeddingComponents(vector, 'replay entry carries');
95
+ const vector = isRecord(entry) ? entry.vector : undefined;
96
+ if (!Array.isArray(vector) || vector.length === 0)
97
+ throw new AiError('AI0003', 'malformed replay entry for the embeddings wire: expected { vector: number[], ms }');
98
+ if (dims !== undefined && vector.length !== dims)
99
+ throw new AiError('AI0003', `replay entry carries ${vector.length} dimensions, expected ${dims}`);
100
+ return verifyEmbeddingComponents(vector, 'replay entry carries');
125
101
  }
126
-
127
102
  /**
128
103
  * A JSON-only copy: what is stored, and what a replay answers, so that a
129
104
  * caller mutating its result never mutates the adapter's entry.
130
105
  * @template T
131
- * @param {T} value
132
- * @returns {T}
133
106
  */
134
107
  export function cloneJson(value) {
135
- return JSON.parse(JSON.stringify(value));
108
+ return JSON.parse(JSON.stringify(value));
136
109
  }
137
-
138
110
  /** Milliseconds now, for the wall time of a purchase. */
139
111
  export function now() {
140
- return globalThis.performance.now();
112
+ return globalThis.performance.now();
141
113
  }
package/src/retry.d.ts CHANGED
@@ -1,34 +1,24 @@
1
1
  /**
2
- * The `retry` option of every client.
3
- * @typedef {Object} RetryOptions
4
- * @property {number} [attempts] - the TOTAL number of tries (default 3;
5
- * 1 disables retrying)
6
- * @property {number} [baseMs] - the first backoff (default 500); each
7
- * later one doubles, with full jitter
8
- * @property {number} [maxMs] - the ceiling on any single wait (default
9
- * 8 000) a provider `Retry-After` included: a provider asking for a
10
- * minute gets the cap, and the value it asked for rides the final
11
- * error as `retryAfterMs` for the caller to honour
12
- * @property {() => number} [random] - the jitter source, for
13
- * deterministic tests
14
- * @property {(ms: number, signal?: AbortSignal) => Promise<void>} [sleep]
15
- * - the wait itself, for deterministic tests; the default is a timer
16
- * that rejects with the abort reason the moment `signal` aborts
17
- */
18
- /**
19
- * The option with its defaults filled in.
20
- * @typedef {Object} RetryPolicy
21
- * @property {number} attempts
22
- * @property {number} baseMs
23
- * @property {number} maxMs
24
- * @property {() => number} random
25
- * @property {(ms: number, signal?: AbortSignal) => Promise<void>} sleep
2
+ * The transport policy every client in this package shares: which
3
+ * failures are transient, how long to wait before trying again, what a
4
+ * provider's `Retry-After` is worth, and how an abort cuts a wait
5
+ * short. The chat wire and the embeddings wire fail alike at the
6
+ * transport a network error, a 408, a 429, a 5xx, a 200 whose body is
7
+ * not what the wire promised — and differ only in what a good reply
8
+ * must carry. So the loop lives here, once, and each client keeps its
9
+ * own reading of a reply: one implementation of retry, two wire shapes.
10
+ *
11
+ * Everything here is internal to the package; the public surface is
12
+ * the `retry` option each client documents.
26
13
  */
14
+ import { AiError } from './errors.ts';
15
+ import { abortError } from '@jarenjs/core/retry';
16
+ export { abortError };
17
+ /** The `retry` option of every client. */
18
+ /** The option with its defaults filled in. */
27
19
  /**
28
- * @param {RetryOptions | undefined} retry
29
- * @returns {RetryPolicy}
30
20
  */
31
- export function normalizeRetry(retry: RetryOptions | undefined): RetryPolicy;
21
+ export declare function normalizeRetry(retry: RetryOptions | undefined): RetryPolicy;
32
22
  /**
33
23
  * Whether a failure is the transient kind. A transport error with a
34
24
  * retryable status is (a network failure before any response counts
@@ -37,20 +27,17 @@ export function normalizeRetry(retry: RetryOptions | undefined): RetryPolicy;
37
27
  * tiers, and safe to retry precisely because nothing was delivered.
38
28
  * Anything else — a caller error, a 401, a 404 — is final on the first
39
29
  * try.
40
- * @param {unknown} err
41
- * @returns {boolean}
42
30
  */
43
- export function isTransientFailure(err: unknown): boolean;
31
+ export declare function isTransientFailure(err: unknown): boolean;
44
32
  /**
45
33
  * The wait before the next try: exponential backoff with full jitter,
46
34
  * capped at `maxMs` — unless the provider named a `Retry-After`, which
47
35
  * wins up to the same cap.
48
- * @param {RetryPolicy} policy
49
- * @param {number} attempt - the try that just failed, counted from 1
50
- * @param {number | undefined} retryAfter - the provider's ask, in ms
51
- * @returns {number} milliseconds
36
+ * @param attempt - the try that just failed, counted from 1
37
+ * @param retryAfter - the provider's ask, in ms
38
+ * @returns milliseconds
52
39
  */
53
- export function retryDelay(policy: RetryPolicy, attempt: number, retryAfter: number | undefined): number;
40
+ export declare function retryDelay(policy: RetryPolicy, attempt: number, retryAfter: number | undefined): number;
54
41
  /**
55
42
  * Run `once` until it settles. A failure `retryable` accepts backs off
56
43
  * and tries again while tries remain; anything else is thrown as it
@@ -59,77 +46,51 @@ export function retryDelay(policy: RetryPolicy, attempt: number, retryAfter: num
59
46
  * during backoff rejects with the abort reason, exactly like an abort
60
47
  * during the request — nothing is ever retried past an abort.
61
48
  * @template T
62
- * @param {RetryPolicy} policy
63
- * @param {() => Promise<T>} once - one request/response cycle
64
- * @param {{ signal?: AbortSignal, retryable: (failure: AiError) => boolean }} options
49
+ * @param once - one request/response cycle
65
50
  * - `retryable` is the wire's own judgment over a coded failure (the
66
51
  * chat client, for one, stops retrying once a streamed delta has
67
52
  * reached the caller); it is never asked about an uncoded error
68
- * @returns {Promise<T>}
69
53
  */
70
- export function withRetry<T>(policy: RetryPolicy, once: () => Promise<T>, options: {
54
+ export declare function withRetry<T>(policy: RetryPolicy, once: () => Promise<T>, options: {
71
55
  signal?: AbortSignal;
72
56
  retryable: (failure: AiError) => boolean;
73
57
  }): Promise<T>;
74
58
  /**
75
59
  * The `AI0002` for a response that is not ok: the status, a short
76
60
  * excerpt of the body, and the provider's `Retry-After` in ms.
77
- * @param {any} response
78
- * @param {string} url
79
- * @returns {Promise<AiError>}
80
61
  */
81
- export function httpFailure(response: any, url: string): Promise<AiError>;
62
+ export declare function httpFailure(response: any, url: string): Promise<AiError>;
82
63
  /**
83
64
  * What to throw when `fetch` itself threw: an abort exactly as it came
84
65
  * (the caller's own signal, never retried, never rewrapped); anything
85
66
  * else the `AI0002` of a failure before any response, status 0.
86
- * @param {any} err
87
- * @param {string} url
88
- * @returns {any}
89
67
  */
90
- export function transportFailure(err: any, url: string): any;
68
+ export declare function transportFailure(err: any, url: string): any;
91
69
  /**
92
70
  * Parse a `Retry-After` header (delta-seconds or HTTP-date) into ms.
93
- * @param {any} response
94
- * @returns {number | undefined}
95
- */
96
- export function retryAfterMs(response: any): number | undefined;
97
- export { abortError };
98
- /**
99
- * The `retry` option of every client.
100
71
  */
72
+ export declare function retryAfterMs(response: any): number | undefined;
101
73
  export type RetryOptions = {
102
- /**
103
- * - the TOTAL number of tries (default 3;
104
- * 1 disables retrying)
105
- */
106
- attempts?: number;
107
- /**
108
- * - the first backoff (default 500); each
109
- * later one doubles, with full jitter
110
- */
111
- baseMs?: number;
112
- /**
113
- * - the ceiling on any single wait (default
114
- * 8 000) — a provider `Retry-After` included: a provider asking for a
115
- * minute gets the cap, and the value it asked for rides the final
116
- * error as `retryAfterMs` for the caller to honour
117
- */
118
- maxMs?: number;
119
- /**
120
- * - the jitter source, for
121
- * deterministic tests
122
- */
123
- random?: () => number;
124
- /**
125
- * - the wait itself, for deterministic tests; the default is a timer
126
- * that rejects with the abort reason the moment `signal` aborts
127
- */
74
+ attempts?: number; /**
75
+ * - the first backoff (default 500); each
76
+ * later one doubles, with full jitter
77
+ */
78
+ baseMs?: number; /**
79
+ * - the ceiling on any single wait (default
80
+ * 8 000) — a provider `Retry-After` included: a provider asking for a
81
+ * minute gets the cap, and the value it asked for rides the final
82
+ * error as `retryAfterMs` for the caller to honour
83
+ */
84
+ maxMs?: number; /**
85
+ * - the jitter source, for
86
+ * deterministic tests
87
+ */
88
+ random?: () => number; /**
89
+ * - the wait itself, for deterministic tests; the default is a timer
90
+ * that rejects with the abort reason the moment `signal` aborts
91
+ */
128
92
  sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
129
93
  };
130
- /**
131
- * The option with its defaults filled in.
132
- */
133
94
  export type RetryPolicy = {
134
95
  attempts: number;
135
96
  baseMs: number;
@@ -137,5 +98,3 @@ export type RetryPolicy = {
137
98
  random: () => number;
138
99
  sleep: (ms: number, signal?: AbortSignal) => Promise<void>;
139
100
  };
140
- import { AiError } from './errors.js';
141
- import { abortError } from '@jarenjs/core/retry';