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.
- package/dist/adapters/hosting/firestoreSessions.js +813 -0
- package/dist/adapters/hosting/firestoreSessions.js.map +1 -0
- package/dist/core/agent/buildToolRegistry.js +1 -1
- package/dist/core/agent/buildToolRegistry.js.map +1 -1
- package/dist/core/agent/toolEffects.js +4 -10
- package/dist/core/agent/toolEffects.js.map +1 -1
- package/dist/core/slots/buildToolsSlot.js +2 -1
- package/dist/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/doors/skill-graph.js +109 -0
- package/dist/doors/skill-graph.js.map +1 -0
- package/dist/esm/adapters/hosting/firestoreSessions.d.ts +474 -0
- package/dist/esm/adapters/hosting/firestoreSessions.js +803 -0
- package/dist/esm/adapters/hosting/firestoreSessions.js.map +1 -0
- package/dist/esm/core/agent/buildToolRegistry.js +2 -2
- package/dist/esm/core/agent/buildToolRegistry.js.map +1 -1
- package/dist/esm/core/agent/toolEffects.d.ts +15 -7
- package/dist/esm/core/agent/toolEffects.js +2 -9
- package/dist/esm/core/agent/toolEffects.js.map +1 -1
- package/dist/esm/core/slots/buildToolsSlot.js +2 -1
- package/dist/esm/core/slots/buildToolsSlot.js.map +1 -1
- package/dist/esm/doors/skill-graph.d.ts +70 -0
- package/dist/esm/doors/skill-graph.js +77 -0
- package/dist/esm/doors/skill-graph.js.map +1 -0
- package/dist/esm/hosting-providers.d.ts +22 -0
- package/dist/esm/hosting-providers.js +27 -0
- package/dist/esm/hosting-providers.js.map +1 -1
- package/dist/esm/lib/injection-engine/devWarn.d.ts +47 -0
- package/dist/esm/lib/injection-engine/devWarn.js +57 -0
- package/dist/esm/lib/injection-engine/devWarn.js.map +1 -0
- package/dist/esm/lib/injection-engine/devWarnHost.d.ts +16 -0
- package/dist/esm/lib/injection-engine/devWarnHost.js +19 -0
- package/dist/esm/lib/injection-engine/devWarnHost.js.map +1 -0
- package/dist/esm/lib/injection-engine/hostContract.d.ts +211 -0
- package/dist/esm/lib/injection-engine/hostContract.js +37 -0
- package/dist/esm/lib/injection-engine/hostContract.js.map +1 -0
- package/dist/esm/lib/injection-engine/index.d.ts +7 -2
- package/dist/esm/lib/injection-engine/index.js +18 -2
- package/dist/esm/lib/injection-engine/index.js.map +1 -1
- package/dist/esm/lib/injection-engine/skillGraph.d.ts +3 -2
- package/dist/esm/lib/injection-engine/skillGraph.js +7 -12
- package/dist/esm/lib/injection-engine/skillGraph.js.map +1 -1
- package/dist/esm/lib/injection-engine/skillIntent.js +4 -7
- package/dist/esm/lib/injection-engine/skillIntent.js.map +1 -1
- package/dist/esm/lib/injection-engine/skillSteps.d.ts +18 -10
- package/dist/esm/lib/injection-engine/skillSteps.js +17 -12
- package/dist/esm/lib/injection-engine/skillSteps.js.map +1 -1
- package/dist/esm/lib/injection-engine/skillToolDescriptors.d.ts +119 -0
- package/dist/esm/lib/injection-engine/skillToolDescriptors.js +182 -0
- package/dist/esm/lib/injection-engine/skillToolDescriptors.js.map +1 -0
- package/dist/esm/lib/injection-engine/skillTools.d.ts +41 -79
- package/dist/esm/lib/injection-engine/skillTools.js +61 -137
- package/dist/esm/lib/injection-engine/skillTools.js.map +1 -1
- package/dist/esm/lib/injection-engine/toolOutcome.d.ts +30 -0
- package/dist/esm/lib/injection-engine/toolOutcome.js +31 -0
- package/dist/esm/lib/injection-engine/toolOutcome.js.map +1 -0
- package/dist/esm/lib/injection-engine/types.d.ts +38 -14
- package/dist/esm/lib/injection-engine/types.js.map +1 -1
- package/dist/hosting-providers.js +34 -1
- package/dist/hosting-providers.js.map +1 -1
- package/dist/lib/injection-engine/devWarn.js +63 -0
- package/dist/lib/injection-engine/devWarn.js.map +1 -0
- package/dist/lib/injection-engine/devWarnHost.js +21 -0
- package/dist/lib/injection-engine/devWarnHost.js.map +1 -0
- package/dist/lib/injection-engine/hostContract.js +38 -0
- package/dist/lib/injection-engine/hostContract.js.map +1 -0
- package/dist/lib/injection-engine/index.js +23 -2
- package/dist/lib/injection-engine/index.js.map +1 -1
- package/dist/lib/injection-engine/skillGraph.js +7 -12
- package/dist/lib/injection-engine/skillGraph.js.map +1 -1
- package/dist/lib/injection-engine/skillIntent.js +4 -7
- package/dist/lib/injection-engine/skillIntent.js.map +1 -1
- package/dist/lib/injection-engine/skillSteps.js +19 -14
- package/dist/lib/injection-engine/skillSteps.js.map +1 -1
- package/dist/lib/injection-engine/skillToolDescriptors.js +187 -0
- package/dist/lib/injection-engine/skillToolDescriptors.js.map +1 -0
- package/dist/lib/injection-engine/skillTools.js +63 -138
- package/dist/lib/injection-engine/skillTools.js.map +1 -1
- package/dist/lib/injection-engine/toolOutcome.js +34 -0
- package/dist/lib/injection-engine/toolOutcome.js.map +1 -0
- package/dist/lib/injection-engine/types.js.map +1 -1
- package/dist/types/adapters/hosting/firestoreSessions.d.ts +475 -0
- package/dist/types/adapters/hosting/firestoreSessions.d.ts.map +1 -0
- package/dist/types/core/agent/buildToolRegistry.d.ts.map +1 -1
- package/dist/types/core/agent/toolEffects.d.ts +15 -7
- package/dist/types/core/agent/toolEffects.d.ts.map +1 -1
- package/dist/types/core/slots/buildToolsSlot.d.ts.map +1 -1
- package/dist/types/doors/skill-graph.d.ts +71 -0
- package/dist/types/doors/skill-graph.d.ts.map +1 -0
- package/dist/types/hosting-providers.d.ts +22 -0
- package/dist/types/hosting-providers.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/devWarn.d.ts +48 -0
- package/dist/types/lib/injection-engine/devWarn.d.ts.map +1 -0
- package/dist/types/lib/injection-engine/devWarnHost.d.ts +17 -0
- package/dist/types/lib/injection-engine/devWarnHost.d.ts.map +1 -0
- package/dist/types/lib/injection-engine/hostContract.d.ts +212 -0
- package/dist/types/lib/injection-engine/hostContract.d.ts.map +1 -0
- package/dist/types/lib/injection-engine/index.d.ts +7 -2
- package/dist/types/lib/injection-engine/index.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/skillGraph.d.ts +3 -2
- package/dist/types/lib/injection-engine/skillGraph.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/skillIntent.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/skillSteps.d.ts +18 -10
- package/dist/types/lib/injection-engine/skillSteps.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/skillToolDescriptors.d.ts +120 -0
- package/dist/types/lib/injection-engine/skillToolDescriptors.d.ts.map +1 -0
- package/dist/types/lib/injection-engine/skillTools.d.ts +41 -79
- package/dist/types/lib/injection-engine/skillTools.d.ts.map +1 -1
- package/dist/types/lib/injection-engine/toolOutcome.d.ts +31 -0
- package/dist/types/lib/injection-engine/toolOutcome.d.ts.map +1 -0
- package/dist/types/lib/injection-engine/types.d.ts +38 -14
- package/dist/types/lib/injection-engine/types.d.ts.map +1 -1
- 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;
|