melchizedek-agents 0.9.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/AGENT_SETUP.md +182 -0
- package/DOCUMENTATION.md +614 -0
- package/LICENSE +21 -0
- package/QUICKSTART.md +164 -0
- package/README.md +151 -0
- package/config/agents/README.md +27 -0
- package/config/agents/examples/ares.yaml +74 -0
- package/config/agents/examples/cartographers.yaml +115 -0
- package/config/agents/examples/claude.yaml +17 -0
- package/config/agents/examples/council.yaml +65 -0
- package/config/agents/examples/critic.yaml +74 -0
- package/config/agents/examples/delegation.yaml +29 -0
- package/config/agents/examples/hierarchical.yaml +26 -0
- package/config/agents/examples/image_production.yaml +112 -0
- package/config/agents/examples/librarian.yaml +75 -0
- package/config/agents/examples/model_zoo.yaml +81 -0
- package/config/agents/examples/patient_advocate.yaml +107 -0
- package/config/agents/examples/scriptorium.yaml +96 -0
- package/config/agents/examples/style_council.yaml +83 -0
- package/config/agents/examples/syndicate.yaml +34 -0
- package/config/agents/examples/tutor.yaml +66 -0
- package/config/agents/syndicateSchema.yaml +291 -0
- package/dist/lib/config.d.ts +108 -0
- package/dist/lib/config.js +110 -0
- package/dist/lib/dispatch.d.ts +133 -0
- package/dist/lib/dispatch.js +182 -0
- package/dist/lib/grounding.d.ts +28 -0
- package/dist/lib/grounding.js +61 -0
- package/dist/lib/index.d.ts +14 -0
- package/dist/lib/index.js +13 -0
- package/dist/lib/loadEnv.d.ts +21 -0
- package/dist/lib/loadEnv.js +58 -0
- package/dist/lib/loadSyndicate.d.ts +177 -0
- package/dist/lib/loadSyndicate.js +264 -0
- package/dist/lib/memory/supabaseMemoryService.d.ts +118 -0
- package/dist/lib/memory/supabaseMemoryService.js +499 -0
- package/dist/lib/models/claudeLlm.d.ts +93 -0
- package/dist/lib/models/claudeLlm.js +332 -0
- package/dist/lib/models/gptLlm.d.ts +110 -0
- package/dist/lib/models/gptLlm.js +497 -0
- package/dist/lib/models/grokLlm.d.ts +64 -0
- package/dist/lib/models/grokLlm.js +86 -0
- package/dist/lib/models/ollamaLlm.d.ts +105 -0
- package/dist/lib/models/ollamaLlm.js +134 -0
- package/dist/lib/models/openAiCompatibleLlm.d.ts +125 -0
- package/dist/lib/models/openAiCompatibleLlm.js +331 -0
- package/dist/lib/models/providerMap.d.ts +28 -0
- package/dist/lib/models/providerMap.js +45 -0
- package/dist/lib/models/registry.d.ts +71 -0
- package/dist/lib/models/registry.js +159 -0
- package/dist/lib/models/schemaNormalize.d.ts +22 -0
- package/dist/lib/models/schemaNormalize.js +58 -0
- package/dist/lib/observability/supabaseSpanExporter.d.ts +35 -0
- package/dist/lib/observability/supabaseSpanExporter.js +115 -0
- package/dist/lib/observability/tracer.d.ts +44 -0
- package/dist/lib/observability/tracer.js +242 -0
- package/dist/lib/persistence/supabaseProvider.d.ts +44 -0
- package/dist/lib/persistence/supabaseProvider.js +88 -0
- package/dist/lib/session/supabaseSessionService.d.ts +26 -0
- package/dist/lib/session/supabaseSessionService.js +193 -0
- package/dist/lib/session/transcript.d.ts +193 -0
- package/dist/lib/session/transcript.js +309 -0
- package/dist/lib/toolRegistry.d.ts +15 -0
- package/dist/lib/toolRegistry.js +58 -0
- package/dist/lib/tools/collectionsSearchTool.d.ts +55 -0
- package/dist/lib/tools/collectionsSearchTool.js +86 -0
- package/dist/lib/tools/generateImageTool.d.ts +16 -0
- package/dist/lib/tools/generateImageTool.js +119 -0
- package/dist/lib/tools/inspectImageTool.d.ts +21 -0
- package/dist/lib/tools/inspectImageTool.js +97 -0
- package/dist/lib/tools/mcpToolFactory.d.ts +2 -0
- package/dist/lib/tools/mcpToolFactory.js +116 -0
- package/dist/lib/tools/toolContract.d.ts +78 -0
- package/dist/lib/tools/toolContract.js +129 -0
- package/dist/lib/tools/webExtractTool.d.ts +70 -0
- package/dist/lib/tools/webExtractTool.js +461 -0
- package/dist/lib/tools/webSearchTool.d.ts +53 -0
- package/dist/lib/tools/webSearchTool.js +126 -0
- package/dist/lib/tools/wikiTools.d.ts +112 -0
- package/dist/lib/tools/wikiTools.js +697 -0
- package/dist/lib/tools/xSearchTool.d.ts +61 -0
- package/dist/lib/tools/xSearchTool.js +125 -0
- package/dist/lib/wiki/agentRun.d.ts +39 -0
- package/dist/lib/wiki/agentRun.js +79 -0
- package/dist/lib/wiki/builder.d.ts +71 -0
- package/dist/lib/wiki/builder.js +165 -0
- package/dist/lib/wiki/dive.d.ts +56 -0
- package/dist/lib/wiki/dive.js +208 -0
- package/dist/lib/wiki/entities.d.ts +220 -0
- package/dist/lib/wiki/entities.js +661 -0
- package/dist/lib/wiki/extract.d.ts +97 -0
- package/dist/lib/wiki/extract.js +305 -0
- package/dist/lib/wiki/fill.d.ts +37 -0
- package/dist/lib/wiki/fill.js +175 -0
- package/dist/lib/wiki/format.d.ts +132 -0
- package/dist/lib/wiki/format.js +177 -0
- package/dist/lib/wiki/graph.d.ts +80 -0
- package/dist/lib/wiki/graph.js +147 -0
- package/dist/lib/wiki/lint.d.ts +58 -0
- package/dist/lib/wiki/lint.js +267 -0
- package/dist/lib/wiki/markdown.d.ts +127 -0
- package/dist/lib/wiki/markdown.js +336 -0
- package/dist/lib/wiki/pipeline.d.ts +69 -0
- package/dist/lib/wiki/pipeline.js +119 -0
- package/dist/lib/wiki/vault.d.ts +84 -0
- package/dist/lib/wiki/vault.js +194 -0
- package/dist/scripts/a2a_server.d.ts +2 -0
- package/dist/scripts/a2a_server.js +997 -0
- package/dist/scripts/syndicate_chat.d.ts +2 -0
- package/dist/scripts/syndicate_chat.js +485 -0
- package/package.json +96 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { BaseSessionService, createSession } from '@google/adk';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import { trimEventForStorage } from "./transcript.js";
|
|
4
|
+
export class SupabaseSessionService extends BaseSessionService {
|
|
5
|
+
supabase;
|
|
6
|
+
constructor(supabaseClient) {
|
|
7
|
+
super();
|
|
8
|
+
this.supabase = supabaseClient;
|
|
9
|
+
}
|
|
10
|
+
getDbId(request) {
|
|
11
|
+
return `${request.appName}:${request.userId}:${request.sessionId}`;
|
|
12
|
+
}
|
|
13
|
+
async createSession(request) {
|
|
14
|
+
const sessionId = request.sessionId || randomUUID();
|
|
15
|
+
const dbId = this.getDbId({ appName: request.appName, userId: request.userId, sessionId });
|
|
16
|
+
const session = createSession({
|
|
17
|
+
id: sessionId,
|
|
18
|
+
appName: request.appName,
|
|
19
|
+
userId: request.userId,
|
|
20
|
+
state: request.state || {},
|
|
21
|
+
events: [],
|
|
22
|
+
lastUpdateTime: Date.now()
|
|
23
|
+
});
|
|
24
|
+
const cleanSession = JSON.parse(JSON.stringify(session));
|
|
25
|
+
const expireAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
26
|
+
const { error } = await this.supabase
|
|
27
|
+
.from('adk_sessions')
|
|
28
|
+
.upsert({
|
|
29
|
+
id: dbId,
|
|
30
|
+
app_name: cleanSession.appName,
|
|
31
|
+
user_id: cleanSession.userId,
|
|
32
|
+
state: cleanSession.state,
|
|
33
|
+
events: cleanSession.events,
|
|
34
|
+
last_update_time: cleanSession.lastUpdateTime,
|
|
35
|
+
expire_at: expireAt
|
|
36
|
+
});
|
|
37
|
+
if (error) {
|
|
38
|
+
console.error('Error creating session in Supabase:', error);
|
|
39
|
+
throw new Error(`Failed to create session: ${error.message}`);
|
|
40
|
+
}
|
|
41
|
+
return session;
|
|
42
|
+
}
|
|
43
|
+
async getSession(request) {
|
|
44
|
+
const dbId = this.getDbId(request);
|
|
45
|
+
const { data, error } = await this.supabase
|
|
46
|
+
.from('adk_sessions')
|
|
47
|
+
.select('*')
|
|
48
|
+
.eq('id', dbId)
|
|
49
|
+
.single();
|
|
50
|
+
if (error) {
|
|
51
|
+
if (error.code === 'PGRST116') {
|
|
52
|
+
return undefined; // Not found
|
|
53
|
+
}
|
|
54
|
+
console.error('Error getting session from Supabase:', error);
|
|
55
|
+
throw new Error(`Failed to get session: ${error.message}`);
|
|
56
|
+
}
|
|
57
|
+
if (!data)
|
|
58
|
+
return undefined;
|
|
59
|
+
const session = {
|
|
60
|
+
id: request.sessionId,
|
|
61
|
+
appName: data.app_name,
|
|
62
|
+
userId: data.user_id,
|
|
63
|
+
state: data.state || {},
|
|
64
|
+
events: data.events || [],
|
|
65
|
+
lastUpdateTime: data.last_update_time
|
|
66
|
+
};
|
|
67
|
+
// Handle GetSessionConfig (numRecentEvents, afterTimestamp) filters
|
|
68
|
+
if (request.config) {
|
|
69
|
+
if (request.config.afterTimestamp) {
|
|
70
|
+
session.events = session.events.filter((e) => e.timestamp && e.timestamp > request.config.afterTimestamp);
|
|
71
|
+
}
|
|
72
|
+
if (request.config.numRecentEvents) {
|
|
73
|
+
session.events = session.events.slice(-request.config.numRecentEvents);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return session;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* ADK's ListSessions contract carries paging: `limit` with either `page`
|
|
80
|
+
* (1-based, wins) or `offset` (0-based), plus an optional sort by last
|
|
81
|
+
* update, and the response must report where in the set the caller is.
|
|
82
|
+
* The window is pushed down to Postgres via .range(), and `count:
|
|
83
|
+
* 'exact'` returns the TOTAL matching rows alongside the page — so
|
|
84
|
+
* totalItems is the real total, not the slice length. With no `limit`,
|
|
85
|
+
* the whole set is one page, which is what the ADK's own docs specify
|
|
86
|
+
* ("Page size used. Equals `totalItems` when no limit was requested").
|
|
87
|
+
*/
|
|
88
|
+
async listSessions(request) {
|
|
89
|
+
const { limit, order } = request;
|
|
90
|
+
const offset = limit !== undefined && request.page !== undefined
|
|
91
|
+
? (Math.max(1, request.page) - 1) * limit
|
|
92
|
+
: (request.offset ?? 0);
|
|
93
|
+
let query = this.supabase
|
|
94
|
+
.from('adk_sessions')
|
|
95
|
+
.select('id, app_name, user_id, state, last_update_time', { count: 'exact' })
|
|
96
|
+
.eq('app_name', request.appName)
|
|
97
|
+
.eq('user_id', request.userId);
|
|
98
|
+
if (order) {
|
|
99
|
+
query = query.order('last_update_time', { ascending: order === 'asc' });
|
|
100
|
+
}
|
|
101
|
+
if (limit !== undefined) {
|
|
102
|
+
query = query.range(offset, offset + limit - 1);
|
|
103
|
+
}
|
|
104
|
+
else if (offset > 0) {
|
|
105
|
+
// An offset without a limit still means "skip these".
|
|
106
|
+
query = query.range(offset, offset + 999_999);
|
|
107
|
+
}
|
|
108
|
+
const { data, error, count } = await query;
|
|
109
|
+
if (error) {
|
|
110
|
+
console.error('Error listing sessions from Supabase:', error);
|
|
111
|
+
throw new Error(`Failed to list sessions: ${error.message}`);
|
|
112
|
+
}
|
|
113
|
+
const sessions = (data || []).map(row => {
|
|
114
|
+
// Extract the original sessionId from the composite key if possible
|
|
115
|
+
const parts = row.id.split(':');
|
|
116
|
+
const originalSessionId = parts.length >= 3 ? parts.slice(2).join(':') : row.id;
|
|
117
|
+
return {
|
|
118
|
+
id: originalSessionId,
|
|
119
|
+
appName: row.app_name,
|
|
120
|
+
userId: row.user_id,
|
|
121
|
+
state: row.state || {},
|
|
122
|
+
events: [], // ListSessions usually returns omitted events to save bandwidth
|
|
123
|
+
lastUpdateTime: row.last_update_time
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
const totalItems = count ?? sessions.length;
|
|
127
|
+
const pageSize = limit ?? totalItems;
|
|
128
|
+
return {
|
|
129
|
+
sessions,
|
|
130
|
+
page: limit ? Math.floor(offset / limit) + 1 : 1,
|
|
131
|
+
limit: pageSize,
|
|
132
|
+
totalItems,
|
|
133
|
+
totalPages: limit ? Math.max(1, Math.ceil(totalItems / limit)) : 1,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
async deleteSession(request) {
|
|
137
|
+
const dbId = this.getDbId(request);
|
|
138
|
+
const { error } = await this.supabase
|
|
139
|
+
.from('adk_sessions')
|
|
140
|
+
.delete()
|
|
141
|
+
.eq('id', dbId);
|
|
142
|
+
if (error) {
|
|
143
|
+
console.error('Error deleting session from Supabase:', error);
|
|
144
|
+
throw new Error(`Failed to delete session: ${error.message}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async appendEvent(request) {
|
|
148
|
+
const { session, event } = request;
|
|
149
|
+
const dbId = this.getDbId({ appName: session.appName, userId: session.userId, sessionId: session.id });
|
|
150
|
+
// Partial events are streaming fragments of an event that is appended
|
|
151
|
+
// whole when it completes. The runner already guards this, but the
|
|
152
|
+
// base contract does too, and a service that persists them would write
|
|
153
|
+
// every token twice.
|
|
154
|
+
if (event.partial)
|
|
155
|
+
return event;
|
|
156
|
+
// Standard ADK internal state merging. This MUST go through the base
|
|
157
|
+
// implementation: it applies `event.actions.stateDelta` to
|
|
158
|
+
// `session.state` and strips `temp:` keys before pushing the event.
|
|
159
|
+
// Re-implementing it as a bare `events.push` (as this did until
|
|
160
|
+
// 2026-08-15) silently drops every state write — `output_key`, any
|
|
161
|
+
// state-injected instruction variable — and leaves `session.state`
|
|
162
|
+
// permanently `{}`, which is exactly what every stored row showed.
|
|
163
|
+
await super.appendEvent({ session, event });
|
|
164
|
+
session.lastUpdateTime = Date.now();
|
|
165
|
+
// Trim on the SERIALIZED COPY only. The live session keeps thought
|
|
166
|
+
// signatures and real tool payloads — the agent's own tool loop reads
|
|
167
|
+
// them back mid-turn — but nothing reads them from the ROW: the
|
|
168
|
+
// projection drops both on the way into a prompt, and the memory
|
|
169
|
+
// service walks `part.text` alone. Across 128 live sessions they were
|
|
170
|
+
// ~90% of every byte stored (73% of it Gemini's `thoughtSignature`),
|
|
171
|
+
// and this method re-uploads the whole array on every event, so those
|
|
172
|
+
// bytes were paid for again on each one. See trimEventForStorage.
|
|
173
|
+
const cleanSession = JSON.parse(JSON.stringify(session));
|
|
174
|
+
cleanSession.events = cleanSession.events.map((e) => trimEventForStorage(e));
|
|
175
|
+
const expireAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString();
|
|
176
|
+
const { error } = await this.supabase
|
|
177
|
+
.from('adk_sessions')
|
|
178
|
+
.upsert({
|
|
179
|
+
id: dbId,
|
|
180
|
+
app_name: cleanSession.appName,
|
|
181
|
+
user_id: cleanSession.userId,
|
|
182
|
+
state: cleanSession.state,
|
|
183
|
+
events: cleanSession.events,
|
|
184
|
+
last_update_time: cleanSession.lastUpdateTime,
|
|
185
|
+
expire_at: expireAt
|
|
186
|
+
});
|
|
187
|
+
if (error) {
|
|
188
|
+
console.error('Error appending event to session in Supabase:', error);
|
|
189
|
+
throw new Error(`Failed to append event: ${error.message}`);
|
|
190
|
+
}
|
|
191
|
+
return event;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-agent transcript sharing.
|
|
3
|
+
*
|
|
4
|
+
* ── The problem this solves ───────────────────────────────────────────────
|
|
5
|
+
* Under plan-dispatch every route runs as its OWN root agent (XScout,
|
|
6
|
+
* Conversationalist, FinancialAnalyst) against ONE shared session. Sharing
|
|
7
|
+
* the session is necessary but not sufficient: ADK decides how a stored
|
|
8
|
+
* event is rendered into a prompt by comparing `event.author` against the
|
|
9
|
+
* agent that is running, and every prior event fails that comparison the
|
|
10
|
+
* moment the route changes. `convertForeignEvent` then rewrites each one to
|
|
11
|
+
* `role: "user"` prefixed with "For context:".
|
|
12
|
+
*
|
|
13
|
+
* Replaying a real production session — a four-turn thread that had passed
|
|
14
|
+
* through three routes — through ADK's own content processor showed what the
|
|
15
|
+
* last route actually received: 15 contents, 118,013 bytes, and EVERY ONE of them
|
|
16
|
+
* `role: "user"`. Not a single `role: "model"` turn in the prompt. The
|
|
17
|
+
* history was all there — and unreadable as a conversation, because nothing
|
|
18
|
+
* in it was marked as having been said by the desk. That is the "it doesn't
|
|
19
|
+
* know what I'm referring to" failure, and no amount of session sharing
|
|
20
|
+
* fixes it, because sharing was never the broken part.
|
|
21
|
+
*
|
|
22
|
+
* Two further leaks rode along in the same conversion. `convertForeignEvent`
|
|
23
|
+
* clones any part it does not recognise verbatim, so the previous route's
|
|
24
|
+
* private chain-of-thought ("**My Micron Monday Decision Strategy** Okay, so
|
|
25
|
+
* the question is…", `thought: true`) arrived as USER speech. And raw tool
|
|
26
|
+
* payloads were inlined as text — one `load_memory` result alone was 23,502
|
|
27
|
+
* characters, one `get_company_fundamentals` 8,953 — all of it handed to a
|
|
28
|
+
* 400-character, tool-less flash-lite route that could do nothing with it.
|
|
29
|
+
*
|
|
30
|
+
* ── The fix ───────────────────────────────────────────────────────────────
|
|
31
|
+
* Project the stored transcript before ADK ever sees it: rewrite each past
|
|
32
|
+
* agent turn to the CURRENT agent's name so it survives as a real
|
|
33
|
+
* `role: "model"` turn, label it with the desk that spoke, and drop what no
|
|
34
|
+
* successor can use — thoughts, tool calls, tool results.
|
|
35
|
+
*
|
|
36
|
+
* The projection is a READ-TIME view. The stored session keeps everything,
|
|
37
|
+
* verbatim, because long-term memory ingestion reads the real record.
|
|
38
|
+
*/
|
|
39
|
+
import { BaseSessionService } from '@google/adk';
|
|
40
|
+
import type { CreateSessionRequest, GetSessionRequest, ListSessionsRequest, ListSessionsResponse, DeleteSessionRequest, Session, Event } from '@google/adk';
|
|
41
|
+
/** Tuning for {@link projectTranscript}. Defaults are the production values. */
|
|
42
|
+
export interface ProjectionOptions {
|
|
43
|
+
/**
|
|
44
|
+
* Character budget for the projected HISTORY, walked backwards from the
|
|
45
|
+
* newest event. Older events are dropped whole once it is exhausted.
|
|
46
|
+
*
|
|
47
|
+
* A bound is required — the session row is a 7-day append-only log and a
|
|
48
|
+
* busy channel reaches six figures of text — but it is set generously:
|
|
49
|
+
* 40,000 characters of tool-free, thought-free prose is many turns, and
|
|
50
|
+
* the analyst route recovers older material through long-term memory.
|
|
51
|
+
*/
|
|
52
|
+
maxHistoryChars?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Ceiling on the NUMBER of past turns kept, whichever binds first with
|
|
55
|
+
* `maxHistoryChars`.
|
|
56
|
+
*
|
|
57
|
+
* A character budget alone is not a bound on prompt shape: a thread of
|
|
58
|
+
* short exchanges fits 43 turns inside 40,000 characters, and a model
|
|
59
|
+
* reading 43 turns of history to answer one question is paying attention
|
|
60
|
+
* tax no budget in bytes describes. Turns and bytes are different costs;
|
|
61
|
+
* both need a ceiling.
|
|
62
|
+
*/
|
|
63
|
+
maxHistoryTurns?: number;
|
|
64
|
+
/** Longest single past turn kept intact; the tail is elided. */
|
|
65
|
+
maxTurnChars?: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Longest tool result kept in the DURABLE record. See {@link trimEventForStorage}.
|
|
69
|
+
*/
|
|
70
|
+
export declare const DEFAULT_MAX_STORED_PAYLOAD_CHARS = 2000;
|
|
71
|
+
/**
|
|
72
|
+
* Render a stored transcript as history the given agent can read.
|
|
73
|
+
*
|
|
74
|
+
* User events pass through untouched. Every agent event becomes a
|
|
75
|
+
* `role: "model"` turn ATTRIBUTED to `forAgent`, which is what keeps ADK
|
|
76
|
+
* from rewriting it into "For context:" user text — with the original
|
|
77
|
+
* author kept as a visible `[Name]` label when it was a different desk, so
|
|
78
|
+
* attribution survives without costing the turn structure.
|
|
79
|
+
*
|
|
80
|
+
* Events that said nothing out loud (a pure tool call, a pure tool result)
|
|
81
|
+
* disappear entirely. Calls and their responses are dropped together, so
|
|
82
|
+
* ADK's function-response pairing never sees a widowed half.
|
|
83
|
+
*
|
|
84
|
+
* Pure and synchronous — see tests/transcript.test.ts.
|
|
85
|
+
*/
|
|
86
|
+
export declare function projectTranscript(events: Event[], forAgent: string, options?: ProjectionOptions): Event[];
|
|
87
|
+
/**
|
|
88
|
+
* Strip from an event what no reader of the STORED session will ever use.
|
|
89
|
+
*
|
|
90
|
+
* ── What the durable record is actually made of ───────────────────────────
|
|
91
|
+
* Measured across 128 live sessions, by part field:
|
|
92
|
+
*
|
|
93
|
+
* thoughtSignature 14.14 MB 73.3%
|
|
94
|
+
* functionResponse.response 2.07 MB 10.8%
|
|
95
|
+
* text (the conversation) 1.86 MB 9.6%
|
|
96
|
+
* toolResponse.response 1.06 MB 5.5%
|
|
97
|
+
*
|
|
98
|
+
* Nine-tenths of every byte stored is machinery. `thoughtSignature` is the
|
|
99
|
+
* opaque base64 blob Gemini attaches for thought continuity — a single part
|
|
100
|
+
* of it reached 115 KB — and tool payloads are the rest, the largest being
|
|
101
|
+
* Gemini's grounding `search_suggestions`, an HTML and CSS widget attached
|
|
102
|
+
* to every web-search result.
|
|
103
|
+
*
|
|
104
|
+
* Beware the shape of this data when measuring it: bucketing a part by
|
|
105
|
+
* whichever tool key it carries attributes the whole part to the tool
|
|
106
|
+
* response, when the weight is in a sibling field. That misattribution said
|
|
107
|
+
* "76% is tool results" and predicted a 70% saving from eliding payloads
|
|
108
|
+
* alone; the real saving from that change was 12%.
|
|
109
|
+
*
|
|
110
|
+
* ── Why none of it is needed ──────────────────────────────────────────────
|
|
111
|
+
* The stored row has exactly two readers. {@link projectTranscript} drops
|
|
112
|
+
* thought parts and tool traffic entirely on the way into a prompt, so a
|
|
113
|
+
* stored signature is never replayed to a model. The memory service's
|
|
114
|
+
* `serializeEvents` walks `part.text` alone, so a payload has never
|
|
115
|
+
* contributed one extracted fact. Everything above is written, re-uploaded
|
|
116
|
+
* on every later event in the turn (this service re-upserts the whole
|
|
117
|
+
* array), and re-fetched whole next turn — for nothing. One live session had
|
|
118
|
+
* reached 1.17 MB; a 60-turn conversation uploaded ~288 MB getting there.
|
|
119
|
+
*
|
|
120
|
+
* ── What survives ─────────────────────────────────────────────────────────
|
|
121
|
+
* A trimmed response KEEPS its shape: `id` and `name` are preserved and only
|
|
122
|
+
* the body is replaced, because ADK pairs calls to responses by id
|
|
123
|
+
* (`rearrangeEventsForLatestFunctionResponse` throws on a widowed half), so
|
|
124
|
+
* an elided result must remain a result. The marker records what was dropped
|
|
125
|
+
* and how big it was — what an operator reading the row needs. Small results
|
|
126
|
+
* pass through untouched: they are cheap, and a short one is occasionally
|
|
127
|
+
* the only record of what a number was.
|
|
128
|
+
*
|
|
129
|
+
* ── The one constraint on changing this ───────────────────────────────────
|
|
130
|
+
* Dropping `thoughtSignature` is safe BECAUSE the projection never feeds
|
|
131
|
+
* stored events back to a model. Anything that starts replaying raw stored
|
|
132
|
+
* events into Gemini would need the signature preserved, or lose thought
|
|
133
|
+
* continuity across a resumed session.
|
|
134
|
+
*
|
|
135
|
+
* Pure: returns a new event, never mutates the input. The caller applies it
|
|
136
|
+
* to the SERIALIZED COPY only — the live in-memory session must keep both
|
|
137
|
+
* signature and payloads, or the agent's own tool loop breaks mid-turn.
|
|
138
|
+
*/
|
|
139
|
+
export declare function trimEventForStorage(event: Event, maxPayloadChars?: number): Event;
|
|
140
|
+
/**
|
|
141
|
+
* A compact plain-text digest of the exchange, for an agent that must be
|
|
142
|
+
* given history as INPUT rather than as conversation.
|
|
143
|
+
*
|
|
144
|
+
* The dispatch classifier is that agent. It runs in its own lane so its JSON
|
|
145
|
+
* verdicts never pollute the user-facing transcript — and the cost of that
|
|
146
|
+
* isolation, in production, was a router that had never seen a single answer
|
|
147
|
+
* it routed. On 2026-08-15 its lane held four user messages and four of its
|
|
148
|
+
* own verdicts, nothing else; a user's counter-argument to the desk's
|
|
149
|
+
* MU/SNDK/NVDA/AMD call therefore read as a remark with no request behind
|
|
150
|
+
* it, and went to small talk. Its own rules ("follow-ups on prior analysis →
|
|
151
|
+
* FinancialAnalyst") were unusable, because no prior analysis was in view.
|
|
152
|
+
*
|
|
153
|
+
* @param maxTurns newest N exchanges to show
|
|
154
|
+
* @param maxCharsPerTurn per-line budget — the router classifies, it does not
|
|
155
|
+
* read; the shape of an answer is enough
|
|
156
|
+
*/
|
|
157
|
+
export declare function renderTranscriptDigest(events: Event[], { maxTurns, maxCharsPerTurn }?: {
|
|
158
|
+
maxTurns?: number;
|
|
159
|
+
maxCharsPerTurn?: number;
|
|
160
|
+
}): string;
|
|
161
|
+
/**
|
|
162
|
+
* A read-time projection over another session service.
|
|
163
|
+
*
|
|
164
|
+
* `getSession` returns the transcript as {@link projectTranscript} renders it
|
|
165
|
+
* for one named agent; every write goes to the REAL session held underneath,
|
|
166
|
+
* so the durable record keeps thoughts, tool calls and full payloads for
|
|
167
|
+
* long-term memory ingestion and for operators reading the row.
|
|
168
|
+
*
|
|
169
|
+
* Both views are kept live during a turn: the runner appends to the
|
|
170
|
+
* projected session it is holding (it re-reads `session.events` before every
|
|
171
|
+
* model call, so the agent's own in-flight tool calls must stay visible and
|
|
172
|
+
* unprojected), while the same event is appended and persisted to the real
|
|
173
|
+
* one. Only history is projected; the current turn never is.
|
|
174
|
+
*
|
|
175
|
+
* Construct one per turn — it is bound to a single agent name.
|
|
176
|
+
*/
|
|
177
|
+
export declare class ProjectedSessionService extends BaseSessionService {
|
|
178
|
+
private readonly inner;
|
|
179
|
+
private readonly forAgent;
|
|
180
|
+
private readonly options;
|
|
181
|
+
/** Real sessions handed out by `getSession`, keyed by identity. */
|
|
182
|
+
private readonly stored;
|
|
183
|
+
constructor(inner: BaseSessionService, forAgent: string, options?: ProjectionOptions);
|
|
184
|
+
private key;
|
|
185
|
+
createSession(request: CreateSessionRequest): Promise<Session>;
|
|
186
|
+
listSessions(request: ListSessionsRequest): Promise<ListSessionsResponse>;
|
|
187
|
+
deleteSession(request: DeleteSessionRequest): Promise<void>;
|
|
188
|
+
getSession(request: GetSessionRequest): Promise<Session | undefined>;
|
|
189
|
+
appendEvent(request: {
|
|
190
|
+
session: Session;
|
|
191
|
+
event: Event;
|
|
192
|
+
}): Promise<Event>;
|
|
193
|
+
}
|