agentfootprint 9.32.0 → 9.34.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.
Files changed (112) hide show
  1. package/dist/adapters/hosting/firestoreSessions.js +813 -0
  2. package/dist/adapters/hosting/firestoreSessions.js.map +1 -0
  3. package/dist/core/agent/buildToolRegistry.js +1 -1
  4. package/dist/core/agent/buildToolRegistry.js.map +1 -1
  5. package/dist/core/agent/toolEffects.js +4 -10
  6. package/dist/core/agent/toolEffects.js.map +1 -1
  7. package/dist/core/slots/buildToolsSlot.js +2 -1
  8. package/dist/core/slots/buildToolsSlot.js.map +1 -1
  9. package/dist/doors/skill-graph.js +109 -0
  10. package/dist/doors/skill-graph.js.map +1 -0
  11. package/dist/esm/adapters/hosting/firestoreSessions.d.ts +474 -0
  12. package/dist/esm/adapters/hosting/firestoreSessions.js +803 -0
  13. package/dist/esm/adapters/hosting/firestoreSessions.js.map +1 -0
  14. package/dist/esm/core/agent/buildToolRegistry.js +2 -2
  15. package/dist/esm/core/agent/buildToolRegistry.js.map +1 -1
  16. package/dist/esm/core/agent/toolEffects.d.ts +15 -7
  17. package/dist/esm/core/agent/toolEffects.js +2 -9
  18. package/dist/esm/core/agent/toolEffects.js.map +1 -1
  19. package/dist/esm/core/slots/buildToolsSlot.js +2 -1
  20. package/dist/esm/core/slots/buildToolsSlot.js.map +1 -1
  21. package/dist/esm/doors/skill-graph.d.ts +70 -0
  22. package/dist/esm/doors/skill-graph.js +77 -0
  23. package/dist/esm/doors/skill-graph.js.map +1 -0
  24. package/dist/esm/hosting-providers.d.ts +22 -0
  25. package/dist/esm/hosting-providers.js +27 -0
  26. package/dist/esm/hosting-providers.js.map +1 -1
  27. package/dist/esm/lib/injection-engine/devWarn.d.ts +47 -0
  28. package/dist/esm/lib/injection-engine/devWarn.js +57 -0
  29. package/dist/esm/lib/injection-engine/devWarn.js.map +1 -0
  30. package/dist/esm/lib/injection-engine/devWarnHost.d.ts +16 -0
  31. package/dist/esm/lib/injection-engine/devWarnHost.js +19 -0
  32. package/dist/esm/lib/injection-engine/devWarnHost.js.map +1 -0
  33. package/dist/esm/lib/injection-engine/hostContract.d.ts +211 -0
  34. package/dist/esm/lib/injection-engine/hostContract.js +37 -0
  35. package/dist/esm/lib/injection-engine/hostContract.js.map +1 -0
  36. package/dist/esm/lib/injection-engine/index.d.ts +7 -2
  37. package/dist/esm/lib/injection-engine/index.js +18 -2
  38. package/dist/esm/lib/injection-engine/index.js.map +1 -1
  39. package/dist/esm/lib/injection-engine/skillGraph.d.ts +3 -2
  40. package/dist/esm/lib/injection-engine/skillGraph.js +7 -12
  41. package/dist/esm/lib/injection-engine/skillGraph.js.map +1 -1
  42. package/dist/esm/lib/injection-engine/skillIntent.js +4 -7
  43. package/dist/esm/lib/injection-engine/skillIntent.js.map +1 -1
  44. package/dist/esm/lib/injection-engine/skillSteps.d.ts +18 -10
  45. package/dist/esm/lib/injection-engine/skillSteps.js +17 -12
  46. package/dist/esm/lib/injection-engine/skillSteps.js.map +1 -1
  47. package/dist/esm/lib/injection-engine/skillToolDescriptors.d.ts +119 -0
  48. package/dist/esm/lib/injection-engine/skillToolDescriptors.js +182 -0
  49. package/dist/esm/lib/injection-engine/skillToolDescriptors.js.map +1 -0
  50. package/dist/esm/lib/injection-engine/skillTools.d.ts +41 -79
  51. package/dist/esm/lib/injection-engine/skillTools.js +61 -137
  52. package/dist/esm/lib/injection-engine/skillTools.js.map +1 -1
  53. package/dist/esm/lib/injection-engine/toolOutcome.d.ts +30 -0
  54. package/dist/esm/lib/injection-engine/toolOutcome.js +31 -0
  55. package/dist/esm/lib/injection-engine/toolOutcome.js.map +1 -0
  56. package/dist/esm/lib/injection-engine/types.d.ts +38 -14
  57. package/dist/esm/lib/injection-engine/types.js.map +1 -1
  58. package/dist/hosting-providers.js +34 -1
  59. package/dist/hosting-providers.js.map +1 -1
  60. package/dist/lib/injection-engine/devWarn.js +63 -0
  61. package/dist/lib/injection-engine/devWarn.js.map +1 -0
  62. package/dist/lib/injection-engine/devWarnHost.js +21 -0
  63. package/dist/lib/injection-engine/devWarnHost.js.map +1 -0
  64. package/dist/lib/injection-engine/hostContract.js +38 -0
  65. package/dist/lib/injection-engine/hostContract.js.map +1 -0
  66. package/dist/lib/injection-engine/index.js +23 -2
  67. package/dist/lib/injection-engine/index.js.map +1 -1
  68. package/dist/lib/injection-engine/skillGraph.js +7 -12
  69. package/dist/lib/injection-engine/skillGraph.js.map +1 -1
  70. package/dist/lib/injection-engine/skillIntent.js +4 -7
  71. package/dist/lib/injection-engine/skillIntent.js.map +1 -1
  72. package/dist/lib/injection-engine/skillSteps.js +19 -14
  73. package/dist/lib/injection-engine/skillSteps.js.map +1 -1
  74. package/dist/lib/injection-engine/skillToolDescriptors.js +187 -0
  75. package/dist/lib/injection-engine/skillToolDescriptors.js.map +1 -0
  76. package/dist/lib/injection-engine/skillTools.js +63 -138
  77. package/dist/lib/injection-engine/skillTools.js.map +1 -1
  78. package/dist/lib/injection-engine/toolOutcome.js +34 -0
  79. package/dist/lib/injection-engine/toolOutcome.js.map +1 -0
  80. package/dist/lib/injection-engine/types.js.map +1 -1
  81. package/dist/types/adapters/hosting/firestoreSessions.d.ts +475 -0
  82. package/dist/types/adapters/hosting/firestoreSessions.d.ts.map +1 -0
  83. package/dist/types/core/agent/buildToolRegistry.d.ts.map +1 -1
  84. package/dist/types/core/agent/toolEffects.d.ts +15 -7
  85. package/dist/types/core/agent/toolEffects.d.ts.map +1 -1
  86. package/dist/types/core/slots/buildToolsSlot.d.ts.map +1 -1
  87. package/dist/types/doors/skill-graph.d.ts +71 -0
  88. package/dist/types/doors/skill-graph.d.ts.map +1 -0
  89. package/dist/types/hosting-providers.d.ts +22 -0
  90. package/dist/types/hosting-providers.d.ts.map +1 -1
  91. package/dist/types/lib/injection-engine/devWarn.d.ts +48 -0
  92. package/dist/types/lib/injection-engine/devWarn.d.ts.map +1 -0
  93. package/dist/types/lib/injection-engine/devWarnHost.d.ts +17 -0
  94. package/dist/types/lib/injection-engine/devWarnHost.d.ts.map +1 -0
  95. package/dist/types/lib/injection-engine/hostContract.d.ts +212 -0
  96. package/dist/types/lib/injection-engine/hostContract.d.ts.map +1 -0
  97. package/dist/types/lib/injection-engine/index.d.ts +7 -2
  98. package/dist/types/lib/injection-engine/index.d.ts.map +1 -1
  99. package/dist/types/lib/injection-engine/skillGraph.d.ts +3 -2
  100. package/dist/types/lib/injection-engine/skillGraph.d.ts.map +1 -1
  101. package/dist/types/lib/injection-engine/skillIntent.d.ts.map +1 -1
  102. package/dist/types/lib/injection-engine/skillSteps.d.ts +18 -10
  103. package/dist/types/lib/injection-engine/skillSteps.d.ts.map +1 -1
  104. package/dist/types/lib/injection-engine/skillToolDescriptors.d.ts +120 -0
  105. package/dist/types/lib/injection-engine/skillToolDescriptors.d.ts.map +1 -0
  106. package/dist/types/lib/injection-engine/skillTools.d.ts +41 -79
  107. package/dist/types/lib/injection-engine/skillTools.d.ts.map +1 -1
  108. package/dist/types/lib/injection-engine/toolOutcome.d.ts +31 -0
  109. package/dist/types/lib/injection-engine/toolOutcome.d.ts.map +1 -0
  110. package/dist/types/lib/injection-engine/types.d.ts +38 -14
  111. package/dist/types/lib/injection-engine/types.d.ts.map +1 -1
  112. package/package.json +20 -2
@@ -0,0 +1,474 @@
1
+ /**
2
+ * firestoreSessions — conversations in Firestore, so a fleet shares them and
3
+ * nobody runs a database.
4
+ *
5
+ * The ladder this rung sits on is already in the package. `memorySessions()`
6
+ * loses everything on restart and says so. `sqliteSessions()` survives a restart
7
+ * on ONE machine and says so. `agentEngineSessions()` is a fleet store, but only
8
+ * for people who already own a Vertex reasoning engine. Firestore is the row for
9
+ * everyone else on this column: a serverless document database with no instance
10
+ * to size, no connection pool to tune, and a free tier — the plainest "many
11
+ * containers, one conversation" answer Google has.
12
+ *
13
+ * ── The shape, said plainly ─────────────────────────────────────────────────
14
+ * One collection. One document per session. Six fields:
15
+ *
16
+ * sessionId · format · savedAt · envelope · owner · messageCount
17
+ *
18
+ * That is the SQLite table, moved. It is deliberately the same shape, because
19
+ * the two stores implement the same port under the same laws, and a reader who
20
+ * has understood one should not have to learn a second model to audit the other.
21
+ *
22
+ * The envelope rides as a JSON **string**, not as a nested map, and that is a
23
+ * decision rather than laziness. A `CheckpointEnvelope` carries arbitrary
24
+ * conversation JSON: keys chosen by a model's tool call, values that may be
25
+ * `undefined`, arrays inside arrays. Firestore refuses all three — a field name
26
+ * may not contain a dot, a tilde, a star, a slash, a bracket or a backtick;
27
+ * `undefined` throws unless the client was
28
+ * built with `ignoreUndefinedProperties`, and a directly nested array is not a
29
+ * representable value. Serialising once at the edge makes every one of those a
30
+ * non-event, at the cost of not being able to query INSIDE a conversation —
31
+ * which no caller of this port has ever asked to do.
32
+ *
33
+ * ── Why the document id is a hash ───────────────────────────────────────────
34
+ * A `sessionId` in this library is OPAQUE. It may be a UUID, an upstream
35
+ * gateway's correlation id, a path-shaped tenant key, or a unicode string a
36
+ * person typed. Firestore document names have rules: no `/`, not `.` or `..`,
37
+ * not matching `__.*__`, and at most 1500 bytes. A session id that broke any of
38
+ * them would fail at the wire on the one turn it mattered — or worse, two ids
39
+ * that differ only past a truncation point would silently become ONE
40
+ * conversation.
41
+ *
42
+ * So the document name is `sha256(domain + NUL + sessionId)` in hex — 64
43
+ * characters, always legal, injective for every input anybody will ever have.
44
+ * The raw id is stored in the `sessionId` FIELD, so a listing can hand it back
45
+ * and a console reader can still see whose document they are looking at.
46
+ *
47
+ * **This is not encryption, and it is important not to read it as any.** The
48
+ * conversation itself is stored in the clear; the hash is an addressing scheme,
49
+ * not a confidentiality control. Anyone who can read the collection can read
50
+ * every conversation in it, and the `sessionId` field beside the hash spells out
51
+ * the id the hash was made from. Two things it DOES cost an operator, stated
52
+ * because they are discovered at the worst moment otherwise:
53
+ *
54
+ * • you cannot look a session up in the Firestore console by typing its raw
55
+ * id — you have to query `sessionId == '…'`, or hash it yourself;
56
+ * • a document name carries no information a human can sort or scan by.
57
+ *
58
+ * Encryption at rest is Google's (always on, and configurable with CMEK).
59
+ * Access control is IAM's. Neither is this adapter's, and neither is implied by
60
+ * the hash.
61
+ *
62
+ * ── The composite index, and the error you get without it ───────────────────
63
+ * `listByUser` runs one server-side query — an equality on `owner`, ordered by
64
+ * `savedAt` descending, paged with a real Firestore cursor. Firestore's
65
+ * automatic single-field indexes do NOT serve that shape: an equality filter on
66
+ * one field ordered by another needs a COMPOSITE index, and until it exists the
67
+ * query fails with gRPC status 9, `FAILED_PRECONDITION`.
68
+ *
69
+ * The index, exactly:
70
+ *
71
+ * collection group : <your collection> (default: agentfootprint_sessions)
72
+ * fields : owner Ascending
73
+ * savedAt Descending
74
+ * __name__ Descending
75
+ *
76
+ * `__name__` is Firestore's document-name field. It is the tiebreaker this
77
+ * adapter orders by explicitly (see {@link FirestoreSessions.listByUser}), and
78
+ * an index's trailing `__name__` takes the direction of the last ordered field —
79
+ * so a console-generated index for `owner ASC, savedAt DESC` is the right one.
80
+ *
81
+ * gcloud firestore indexes composite create \
82
+ * --collection-group=agentfootprint_sessions \
83
+ * --field-config=field-path=owner,order=ascending \
84
+ * --field-config=field-path=savedAt,order=descending \
85
+ * --database='(default)'
86
+ *
87
+ * `--database` is spelled out rather than left to gcloud's default, and it must
88
+ * match the `database` this store was built with. gcloud assumes `(default)` when
89
+ * the flag is absent, so an operator on a NAMED database who follows a command
90
+ * without it creates the index somewhere else and gets the identical failure
91
+ * back, with nothing to suggest why.
92
+ *
93
+ * When the index is missing this adapter raises {@link FirestoreIndexMissingError},
94
+ * which prints that same line with your collection and database already filled
95
+ * in, and names those fields rather than restating the service's message — see
96
+ * {@link firestoreFailure} for why no Google text is ever echoed here.
97
+ *
98
+ * ── The ceiling, since a store should name its own ──────────────────────────
99
+ * A Firestore document is capped at 1 MiB. A conversation whose stored envelope
100
+ * approaches that is refused BY NAME before the write
101
+ * ({@link EnvelopeTooLargeError}) rather than being sent and rejected as an
102
+ * opaque `INVALID_ARGUMENT`. Nothing is ever truncated: half a conversation
103
+ * stored as if it were whole is the failure this whole file exists to avoid.
104
+ * Compaction (the builder's `.compaction({ … })`, or `.window()`) is the
105
+ * answer, and the refusal says so. Named from the builder deliberately: there
106
+ * is no `agent.compact()` to call, and a doc comment ships into the emitted
107
+ * `.d.ts`, so a method named here that does not exist is a method somebody
108
+ * types on hover.
109
+ *
110
+ * ── The laws it inherits rather than re-implements ──────────────────────────
111
+ * `checkEnvelope` runs on the way OUT and on the way IN, so an envelope whose
112
+ * `format` this runtime does not know is refused by name, and a session that is
113
+ * PRESENT but unreadable is refused by name too. Only a session that was never
114
+ * written hydrates as `undefined`. A conversation that exists and cannot be read
115
+ * must never be answered with a fresh start — from the outside that is
116
+ * indistinguishable from a brand-new user.
117
+ *
118
+ * Ownership is DERIVED from the stored envelope and established ONCE. See
119
+ * `persist` below: SQLite gets that from `COALESCE(sessions.owner,
120
+ * excluded.owner)`, Firestore has no such thing, and a `set({ merge: true })`
121
+ * would let the last writer win — which is exactly the bug, not a workaround
122
+ * for it. So the write is a transaction.
123
+ *
124
+ * ── How much of this is verified ────────────────────────────────────────────
125
+ * **Contract-shaped and tested. NOT field-validated.** Nothing here has been run
126
+ * against a live Firestore by this repository.
127
+ *
128
+ * The pin is the `firestoreSessions` row of `GOOGLE_SURFACE_PINS` in
129
+ * `test/adapters/google/googlePin.ts`, asserted by
130
+ * `test/adapters/google/google-surface-pin.test.ts`. Its 18 members were
131
+ * hand-verified against a real `@google-cloud/firestore` 9.0.0 install in a
132
+ * scratch project OUTSIDE this repository: seventeen read off
133
+ * `types/firestore.d.ts` before a line of this file was written, and
134
+ * `DocumentSnapshot.id` verified afterwards, when a review found the row had
135
+ * pinned `DocumentReference.id` — real, but a member this adapter never reads —
136
+ * in place of the one the cursor actually reads.
137
+ *
138
+ * That package is deliberately NOT installed here. It depends on
139
+ * `@opentelemetry/api`, so installing it hoists that package to the repository
140
+ * root and disarms `test/observability-providers/otel.test.ts`, which proves
141
+ * `otelObservability()` refuses BY NAME when `@opentelemetry/api` is absent.
142
+ * The consequence has to be said plainly: **the reality assertion — "every
143
+ * pinned member really exists on the real package" — SKIPS in this repository.**
144
+ * It runs in full for anyone who installs `@google-cloud/firestore` locally.
145
+ *
146
+ * So what is machine-checked in CI is the SHAPE pin, not the reality pin: this
147
+ * adapter dispatches exactly the members the row names and no others, every run,
148
+ * everywhere. That the row spells those members the way Google does is held by a
149
+ * hand check against a real install, not by a test that runs here.
150
+ *
151
+ * The DESIGN is informed by an independent field trial of a different Firestore
152
+ * session adapter, which ran against a real Firestore and passed eight ownership
153
+ * and history checks — and whose own report named the defect this adapter does
154
+ * not reproduce: that adapter read every document for one owner, sorted them in
155
+ * the client, and applied an offset cursor. That works until one person has a
156
+ * lot of conversations, and then it costs a full read of all of them per page.
157
+ * What the trial proves is that the ownership and history SEMANTICS survive a
158
+ * real service; it proves nothing about this file's query, cursor, transaction
159
+ * or index, because that adapter had none of them.
160
+ */
161
+ import type { SessionLifecycle } from '../../hosting/types.js';
162
+ /** One document as it came back. `firestore.d.ts` line 1605. */
163
+ export interface FirestoreDocumentSnapshotLike {
164
+ /** A PROPERTY, not a method — `readonly exists: boolean`. */
165
+ readonly exists: boolean;
166
+ readonly id: string;
167
+ /** `undefined` when the document does not exist. */
168
+ data(): Record<string, unknown> | undefined;
169
+ }
170
+ /** The answer to a query. `firestore.d.ts` line 2163. */
171
+ export interface FirestoreQuerySnapshotLike {
172
+ /**
173
+ * An ARRAY, already materialised. Not an async iterable, not a stream — the
174
+ * SDK has `Query.stream()` for that and this adapter does not use it. A
175
+ * double that hands back an async iterable here would be testing a client
176
+ * nobody ships.
177
+ */
178
+ readonly docs: readonly FirestoreDocumentSnapshotLike[];
179
+ }
180
+ /** The query builder. Every method is SYNCHRONOUS and returns a NEW query
181
+ * (the SDK documents the immutability explicitly); only `get` is async.
182
+ * `firestore.d.ts` line 1714. */
183
+ export interface FirestoreQueryLike {
184
+ where(fieldPath: string, opStr: string, value: unknown): FirestoreQueryLike;
185
+ orderBy(fieldPath: string | unknown, directionStr?: 'asc' | 'desc'): FirestoreQueryLike;
186
+ startAfter(...fieldValues: unknown[]): FirestoreQueryLike;
187
+ limit(limit: number): FirestoreQueryLike;
188
+ get(): Promise<FirestoreQuerySnapshotLike>;
189
+ }
190
+ /** A handle on one document. `firestore.d.ts` line 1436. */
191
+ export interface FirestoreDocumentReferenceLike {
192
+ readonly id: string;
193
+ get(): Promise<FirestoreDocumentSnapshotLike>;
194
+ /** Resolves to a `WriteResult`, which this adapter does not read. */
195
+ delete(): Promise<unknown>;
196
+ }
197
+ /** A collection is a Query that can also mint document handles.
198
+ * `firestore.d.ts` line 2305. */
199
+ export interface FirestoreCollectionLike extends FirestoreQueryLike {
200
+ doc(documentPath: string): FirestoreDocumentReferenceLike;
201
+ }
202
+ /**
203
+ * The transaction handle. `firestore.d.ts` line 801.
204
+ *
205
+ * The asymmetry is the thing to get right in a double: `get` is ASYNC and
206
+ * answers a snapshot, while `set` is SYNCHRONOUS and answers the transaction
207
+ * itself for chaining. A double whose `set` returned a promise would let an
208
+ * adapter that forgot to sequence its writes pass a test it should fail.
209
+ */
210
+ export interface FirestoreTransactionLike {
211
+ get(documentRef: FirestoreDocumentReferenceLike): Promise<FirestoreDocumentSnapshotLike>;
212
+ set(documentRef: FirestoreDocumentReferenceLike, data: Record<string, unknown>): FirestoreTransactionLike;
213
+ }
214
+ /** One connected database. `firestore.d.ts` line 554. */
215
+ export interface FirestoreLike {
216
+ collection(collectionPath: string): FirestoreCollectionLike;
217
+ runTransaction<T>(updateFunction: (transaction: FirestoreTransactionLike) => Promise<T>): Promise<T>;
218
+ /** Closes the client's gRPC channels. See {@link FirestoreSessions.close}. */
219
+ terminate(): Promise<void>;
220
+ }
221
+ /**
222
+ * The two exports this adapter needs from the package.
223
+ *
224
+ * `FieldPath.documentId()` is a STATIC that returns a sentinel; it is how a
225
+ * query orders by document name, and there is no string spelling of it that the
226
+ * client will accept in `orderBy`.
227
+ */
228
+ export interface FirestoreConstructorLike {
229
+ new (settings?: Record<string, unknown>): FirestoreLike;
230
+ }
231
+ /** The module's shape, as this adapter loads it. */
232
+ export interface FirestoreSdkModule {
233
+ readonly Firestore: FirestoreConstructorLike;
234
+ readonly FieldPath: {
235
+ documentId(): unknown;
236
+ };
237
+ }
238
+ /** Options for {@link firestoreSessions}. */
239
+ export interface FirestoreSessionsOptions {
240
+ /**
241
+ * The Google Cloud project. Omit and the client reads it from the ambient
242
+ * environment the same way every Google client does (`GCLOUD_PROJECT`, or the
243
+ * Application Default Credentials).
244
+ */
245
+ readonly project?: string;
246
+ /**
247
+ * Which Firestore database in that project. Omit for `'(default)'`.
248
+ *
249
+ * Worth setting deliberately: a project can hold several databases, and
250
+ * "conversations went to the wrong one" looks exactly like "conversations
251
+ * were lost".
252
+ */
253
+ readonly database?: string;
254
+ /**
255
+ * The collection sessions live in. Default
256
+ * {@link DEFAULT_SESSION_COLLECTION}.
257
+ *
258
+ * A top-level collection name, not a path — this adapter does not nest
259
+ * sessions under another document, because a store that required a parent
260
+ * would be making a decision about your data model that the port never asked
261
+ * for.
262
+ */
263
+ readonly collection?: string;
264
+ /**
265
+ * A Firestore client you already built.
266
+ *
267
+ * Most applications that reach for this adapter already have one, and two
268
+ * clients in one process means two sets of gRPC channels for no benefit. When
269
+ * you pass one, this store never terminates it — see
270
+ * {@link FirestoreSessions.close}.
271
+ *
272
+ * Passing this together with `project` or `database` is refused rather than
273
+ * silently ignored: those settings belong to whoever CONSTRUCTED the client,
274
+ * and accepting them here would let a caller believe they had switched
275
+ * databases.
276
+ *
277
+ * It does NOT remove the need for the package to be installed:
278
+ * `FieldPath.documentId()` is a static on the module, and there is no string
279
+ * spelling of `__name__` the client accepts in `orderBy`. In practice that
280
+ * costs nothing — anyone holding a client already has the package — but it is
281
+ * stated rather than discovered, because "I passed my own client, why is it
282
+ * still loading the module?" is a fair question to have answered here.
283
+ */
284
+ readonly firestore?: FirestoreLike;
285
+ /**
286
+ * @internal Test seam only — the `@google-cloud/firestore` module, injected.
287
+ * Lets the suite exercise every path (including the refusals) without a
288
+ * credential or a network. Not public API, and not a place to plug in another
289
+ * Firestore driver.
290
+ */
291
+ readonly _sdk?: FirestoreSdkModule;
292
+ }
293
+ /** Where sessions live when the caller names no collection. */
294
+ export declare const DEFAULT_SESSION_COLLECTION = "agentfootprint_sessions";
295
+ /**
296
+ * Firestore's hard ceiling on one document, in bytes. Not ours — the service's.
297
+ *
298
+ * @see https://cloud.google.com/firestore/quotas
299
+ */
300
+ export declare const FIRESTORE_MAX_DOCUMENT_BYTES = 1048576;
301
+ /**
302
+ * The largest stored envelope this adapter will attempt, in bytes.
303
+ *
304
+ * Below the real ceiling by a margin, because the document also carries five
305
+ * other fields, their NAMES, and the document's own path — all of which count
306
+ * toward Firestore's total. Refusing a little early with a sentence that says
307
+ * what to do beats sending a 1,048,570-byte envelope and getting back an
308
+ * `INVALID_ARGUMENT` that names nothing.
309
+ */
310
+ export declare const FIRESTORE_MAX_ENVELOPE_BYTES: number;
311
+ /**
312
+ * A session store in Firestore.
313
+ *
314
+ * A {@link SessionLifecycle} plus the three things a real store owns beyond the
315
+ * port — forgetting, closing, and telling you where it is writing — because the
316
+ * port deliberately asks for two methods and leaves the rest to whoever
317
+ * implements it.
318
+ */
319
+ export interface FirestoreSessions extends SessionLifecycle {
320
+ /** The collection these sessions live in. Useful in an incident. */
321
+ readonly collection: string;
322
+ /** Forget one session. A session that was never there is not an error. */
323
+ forget(sessionId: string): Promise<void>;
324
+ /**
325
+ * The document name one session id maps to — the sha-256 above.
326
+ *
327
+ * Exposed because the mapping is one-way and an operator in an incident needs
328
+ * it: this is the string to paste into the Firestore console to find one
329
+ * conversation. It is a pure function, it touches nothing, and it works on a
330
+ * closed store.
331
+ */
332
+ documentIdFor(sessionId: string): string;
333
+ /**
334
+ * Stop using this store, and release the client's gRPC channels.
335
+ *
336
+ * **Async, unlike the other stores' `close()`, and that is not an
337
+ * inconsistency for its own sake.** Terminating a Firestore client closes
338
+ * channels; a Node process holding an open channel does not exit. A `void`
339
+ * return here would be a promise this adapter could not keep, so the shape
340
+ * says what actually happens and a shutdown hook can await it.
341
+ *
342
+ * Idempotent, and **final** — reading or writing afterwards refuses by name
343
+ * rather than quietly reconnecting, because a store that reopened behind you
344
+ * would hide a shutdown-ordering bug instead of surfacing it.
345
+ *
346
+ * A client you passed in yourself is NOT terminated: this store did not open
347
+ * it and does not get to decide when the rest of your application stops using
348
+ * it. Nothing is torn down on Google's side either — the documents outlive the
349
+ * process, which is the entire reason to use a managed store.
350
+ */
351
+ close(): Promise<void>;
352
+ }
353
+ /**
354
+ * Raised when the query `listByUser` needs has no composite index yet.
355
+ *
356
+ * Its own class rather than a generic failure, because this is the ONE
357
+ * Firestore error an operator can fix in sixty seconds — and the only way they
358
+ * will know that is if the message says which index, on which collection, in
359
+ * which DATABASE, in which order. See the module header for the `gcloud` line.
360
+ *
361
+ * The database is named and the `--database` flag is always printed, including
362
+ * for `(default)`, where gcloud would have assumed it anyway. That is deliberate:
363
+ * a project may hold several Firestore databases, and an operator on a
364
+ * non-default one who follows a command with no `--database` creates the index on
365
+ * `(default)` and gets this identical error back. A refusal that teaches the
366
+ * wrong fix is worse than a bare failure, and one always-present flag costs
367
+ * nothing to be right.
368
+ *
369
+ * The service's own message carries a one-click creation link and is
370
+ * deliberately NOT echoed: it restates the failing query, and the failing query
371
+ * contains a user id. See {@link firestoreFailure}.
372
+ */
373
+ export declare class FirestoreIndexMissingError extends Error {
374
+ readonly code: "ERR_FIRESTORE_INDEX_MISSING";
375
+ /** The collection whose index is missing. */
376
+ readonly collection: string;
377
+ /**
378
+ * The database it lives in, or `undefined` when this store did not build the
379
+ * client and therefore cannot know — see the message for what to do then.
380
+ */
381
+ readonly database: string | undefined;
382
+ constructor(collection: string, database?: string);
383
+ }
384
+ /**
385
+ * Raised when a conversation is too big to be one Firestore document.
386
+ *
387
+ * Refused BEFORE the write, so the failure names the conversation and the fix
388
+ * rather than arriving as an opaque `INVALID_ARGUMENT` from the wire. Nothing is
389
+ * truncated on the way past: a conversation half-stored as if it were whole is
390
+ * the exact failure this file's other laws exist to prevent.
391
+ */
392
+ export declare class EnvelopeTooLargeError extends Error {
393
+ readonly code: "ERR_ENVELOPE_TOO_LARGE";
394
+ /** The session that could not be stored. */
395
+ readonly sessionId: string;
396
+ /** How big its serialized envelope was. */
397
+ readonly bytes: number;
398
+ constructor(sessionId: string, bytes: number);
399
+ }
400
+ /**
401
+ * Conversations in Firestore — a fleet-shared session store with no instance to
402
+ * run.
403
+ *
404
+ * **Status: contract-shaped and tested, NOT field-validated.** Every SDK member
405
+ * it calls was read off a real install of `@google-cloud/firestore` 9.0.0 and
406
+ * hand-verified there; the test that re-checks those names against the real
407
+ * package SKIPS in this repository, because the package is deliberately not
408
+ * installed here. What runs in CI is the dispatch pin. No test here pretends to
409
+ * have reached Google. See the module header for the full account, and for what
410
+ * a field trial of a DIFFERENT adapter did and did not establish about this
411
+ * design.
412
+ *
413
+ * @throws FirestoreIndexMissingError from `listByUser` until the composite index
414
+ * exists — see the module header for the exact index.
415
+ * @throws EnvelopeTooLargeError from `persist` for a conversation above
416
+ * {@link FIRESTORE_MAX_ENVELOPE_BYTES}. Never truncated.
417
+ *
418
+ * @example A standing agent whose conversations are shared across instances
419
+ * import { standingAgent, nodeHost } from 'agentfootprint/hosting';
420
+ * import { firestoreSessions } from 'agentfootprint/hosting';
421
+ *
422
+ * const sessions = firestoreSessions({ project: 'my-project' });
423
+ * const handle = await standingAgent({
424
+ * agentFactory: () => buildAgent(),
425
+ * host: nodeHost({ port: 8080 }),
426
+ * sessions,
427
+ * });
428
+ * process.on('SIGTERM', () => void handle.close().then(() => sessions.close()));
429
+ *
430
+ * @example Reusing the Firestore client the application already has
431
+ * const sessions = firestoreSessions({ firestore: db, collection: 'chat_sessions' });
432
+ * // close() will NOT terminate `db` — this store did not open it.
433
+ */
434
+ export declare function firestoreSessions(options?: FirestoreSessionsOptions): FirestoreSessions;
435
+ /**
436
+ * The document name for one session id — `sha256(domain ‖ NUL ‖ id)` in hex.
437
+ *
438
+ * A module-level pure function rather than a closure, so the same mapping is
439
+ * available to the store, to a test, and to an operator who needs it in a REPL.
440
+ * The NUL separator is what stops `domain + "a" + "bc"` and `domain + "ab" + "c"`
441
+ * from being the same input; a session id may legally contain anything else.
442
+ *
443
+ * See the module header for why this is an ADDRESSING scheme and not, in any
444
+ * sense, encryption.
445
+ */
446
+ export declare function documentIdFor(sessionId: string): string;
447
+ /**
448
+ * The gRPC status of a failed call, as a NAME, wherever the client put it.
449
+ *
450
+ * Two spellings are accepted because two layers report it differently: the gax
451
+ * layer sets a numeric `code`, and some wrappers carry the name as a string. A
452
+ * classifier that read only one of them would quietly stop classifying the day
453
+ * the client is upgraded.
454
+ */
455
+ export declare function grpcStatusOf(err: unknown): string | undefined;
456
+ /** Is this the service saying "that query has no index"? */
457
+ export declare function isFailedPrecondition(err: unknown): boolean;
458
+ /**
459
+ * Re-raise a failed Firestore call **without its text** — the same law the AWS
460
+ * and Vertex columns follow, re-aimed at a gRPC error.
461
+ *
462
+ * What comes through is the part that is both safe and actionable: which
463
+ * operation failed, which collection it was on, and the gRPC status name. What
464
+ * does not is the SDK's message, because a Firestore error restates the failing
465
+ * request — a document path, a filter value, a field — and those carry a user id
466
+ * and a whole conversation's state. An error thrown from an adapter reaches the
467
+ * model as a tool result AND rides the event stream to every sink attached to
468
+ * the agent.
469
+ *
470
+ * No credential is ever named. **The original is deliberately not attached as
471
+ * `cause`** — a cause travels with the error into every serializer that walks
472
+ * own properties, which would undo all of this in one `JSON.stringify`.
473
+ */
474
+ export declare function firestoreFailure(operation: string, collection: string, err: unknown): Error;