@opengeni/db 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-O3D7DABC.js → chunk-B22X3IEZ.js} +369 -49
- package/dist/chunk-B22X3IEZ.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +15133 -13199
- package/dist/index.js.map +1 -1
- package/dist/provision-roles.d.ts +475 -211
- package/dist/{schema-Dp2MbBxx.d.ts → schema-BN5mB9xZ.d.ts} +1365 -256
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +13 -3
- package/drizzle/0063_session_control_mega_foundation.sql +1324 -0
- package/package.json +1 -5
- package/src/index.ts +1474 -2898
- package/src/schema.ts +391 -60
- package/src/session-control.ts +1759 -0
- package/src/session-queue-commands.ts +1753 -0
- package/src/session-tool-call-settlement.ts +269 -0
- package/dist/chunk-O3D7DABC.js.map +0 -1
- package/src/session-control-cutover-audit.ts +0 -1203
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import type { SessionEvent } from "@opengeni/contracts";
|
|
2
|
+
import { and, asc, eq, sql } from "drizzle-orm";
|
|
3
|
+
import type { Database } from "./index";
|
|
4
|
+
import { sanitizeEventPayload, sanitizeModelPayload } from "./event-payload-sanitizer";
|
|
5
|
+
import * as schema from "./schema";
|
|
6
|
+
|
|
7
|
+
export const TOOL_RESULT_TYPE_BY_CALL_TYPE: Readonly<Record<string, string>> = {
|
|
8
|
+
function_call: "function_call_result",
|
|
9
|
+
computer_call: "computer_call_result",
|
|
10
|
+
shell_call: "shell_call_output",
|
|
11
|
+
apply_patch_call: "apply_patch_call_output",
|
|
12
|
+
tool_search_call: "tool_search_output",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function historyCallId(item: Record<string, unknown>): string | null {
|
|
16
|
+
const value = item.callId ?? item.call_id ?? item.id;
|
|
17
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function historyItemType(item: Record<string, unknown>): string | null {
|
|
21
|
+
return typeof item.type === "string" ? item.type : null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Provider-valid result for a started call whose durable outcome is unknown. */
|
|
25
|
+
export function interruptedToolCallResult(input: {
|
|
26
|
+
callType: string;
|
|
27
|
+
callId: string;
|
|
28
|
+
callItem: Record<string, unknown>;
|
|
29
|
+
reason: string;
|
|
30
|
+
}): Record<string, unknown> | null {
|
|
31
|
+
const message =
|
|
32
|
+
`Tool execution was interrupted by ${input.reason} before its result was durably recorded. ` +
|
|
33
|
+
"The side-effect outcome is unknown; inspect actual state before repeating the call.";
|
|
34
|
+
if (input.callType === "function_call") {
|
|
35
|
+
const name = typeof input.callItem.name === "string" ? input.callItem.name : "tool";
|
|
36
|
+
return {
|
|
37
|
+
type: "function_call_result",
|
|
38
|
+
name,
|
|
39
|
+
...(typeof input.callItem.namespace === "string"
|
|
40
|
+
? { namespace: input.callItem.namespace }
|
|
41
|
+
: {}),
|
|
42
|
+
callId: input.callId,
|
|
43
|
+
status: "incomplete",
|
|
44
|
+
output: { type: "text", text: message },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (input.callType === "shell_call") {
|
|
48
|
+
return {
|
|
49
|
+
type: "shell_call_output",
|
|
50
|
+
callId: input.callId,
|
|
51
|
+
output: [{ stdout: "", stderr: message, outcome: { type: "exit", exitCode: null } }],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
if (input.callType === "apply_patch_call") {
|
|
55
|
+
return {
|
|
56
|
+
type: "apply_patch_call_output",
|
|
57
|
+
callId: input.callId,
|
|
58
|
+
status: "failed",
|
|
59
|
+
output: message,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (input.callType === "computer_call") return null;
|
|
63
|
+
if (input.callType === "tool_search_call") {
|
|
64
|
+
const snakeId = typeof input.callItem.call_id === "string";
|
|
65
|
+
return {
|
|
66
|
+
type: "tool_search_output",
|
|
67
|
+
...(snakeId ? { call_id: input.callId } : { callId: input.callId }),
|
|
68
|
+
...(input.callItem.execution === "client" || input.callItem.execution === "server"
|
|
69
|
+
? { execution: input.callItem.execution }
|
|
70
|
+
: {}),
|
|
71
|
+
status: "incomplete",
|
|
72
|
+
tools: [],
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type ClosePendingSessionToolCallsInput = {
|
|
79
|
+
accountId: string;
|
|
80
|
+
workspaceId: string;
|
|
81
|
+
sessionId: string;
|
|
82
|
+
turnId: string;
|
|
83
|
+
reason: string;
|
|
84
|
+
sequence: number;
|
|
85
|
+
now: Date;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
function mapEvent(row: typeof schema.sessionEvents.$inferSelect): SessionEvent {
|
|
89
|
+
return {
|
|
90
|
+
id: row.id,
|
|
91
|
+
workspaceId: row.workspaceId,
|
|
92
|
+
sessionId: row.sessionId,
|
|
93
|
+
sequence: row.sequence,
|
|
94
|
+
type: row.type as SessionEvent["type"],
|
|
95
|
+
payload: row.payload,
|
|
96
|
+
occurredAt: row.occurredAt.toISOString(),
|
|
97
|
+
clientEventId: row.clientEventId,
|
|
98
|
+
turnId: row.turnId,
|
|
99
|
+
turnGeneration: row.turnGeneration,
|
|
100
|
+
turnAttemptId: row.turnAttemptId,
|
|
101
|
+
turnAssociation: row.turnAssociation as SessionEvent["turnAssociation"],
|
|
102
|
+
duplicateOfEventId: row.duplicateOfEventId,
|
|
103
|
+
duplicateReason: row.duplicateReason,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Close every unresolved raw tool call for a logical turn while its owning
|
|
109
|
+
* session/turn locks are held. Durable results win; otherwise the model gets an
|
|
110
|
+
* explicit interrupted/outcome-unknown result for protocols that can represent
|
|
111
|
+
* one. Repeated settlement is an exact no-op after ledger deletion.
|
|
112
|
+
*/
|
|
113
|
+
export async function closePendingSessionToolCallsInTransaction(
|
|
114
|
+
tx: Database,
|
|
115
|
+
input: ClosePendingSessionToolCallsInput,
|
|
116
|
+
): Promise<{ sequence: number; events: SessionEvent[]; closed: number }> {
|
|
117
|
+
const pending = await tx
|
|
118
|
+
.select()
|
|
119
|
+
.from(schema.sessionPendingToolCalls)
|
|
120
|
+
.where(
|
|
121
|
+
and(
|
|
122
|
+
eq(schema.sessionPendingToolCalls.workspaceId, input.workspaceId),
|
|
123
|
+
eq(schema.sessionPendingToolCalls.sessionId, input.sessionId),
|
|
124
|
+
eq(schema.sessionPendingToolCalls.turnId, input.turnId),
|
|
125
|
+
),
|
|
126
|
+
)
|
|
127
|
+
.orderBy(asc(schema.sessionPendingToolCalls.createdAt), asc(schema.sessionPendingToolCalls.id))
|
|
128
|
+
.for("update");
|
|
129
|
+
if (pending.length === 0) return { sequence: input.sequence, events: [], closed: 0 };
|
|
130
|
+
|
|
131
|
+
const history = await tx
|
|
132
|
+
.select({ item: schema.sessionHistoryItems.item })
|
|
133
|
+
.from(schema.sessionHistoryItems)
|
|
134
|
+
.where(
|
|
135
|
+
and(
|
|
136
|
+
eq(schema.sessionHistoryItems.workspaceId, input.workspaceId),
|
|
137
|
+
eq(schema.sessionHistoryItems.sessionId, input.sessionId),
|
|
138
|
+
eq(schema.sessionHistoryItems.turnId, input.turnId),
|
|
139
|
+
eq(schema.sessionHistoryItems.active, true),
|
|
140
|
+
),
|
|
141
|
+
)
|
|
142
|
+
.orderBy(asc(schema.sessionHistoryItems.position));
|
|
143
|
+
const [{ maxPosition } = { maxPosition: -1 }] = await tx
|
|
144
|
+
.select({ maxPosition: sql<number>`coalesce(max(${schema.sessionHistoryItems.position}), -1)` })
|
|
145
|
+
.from(schema.sessionHistoryItems)
|
|
146
|
+
.where(
|
|
147
|
+
and(
|
|
148
|
+
eq(schema.sessionHistoryItems.workspaceId, input.workspaceId),
|
|
149
|
+
eq(schema.sessionHistoryItems.sessionId, input.sessionId),
|
|
150
|
+
),
|
|
151
|
+
);
|
|
152
|
+
let nextPosition = Math.floor(Number(maxPosition)) + 1;
|
|
153
|
+
let sequence = input.sequence;
|
|
154
|
+
const historyValues: Array<typeof schema.sessionHistoryItems.$inferInsert> = [];
|
|
155
|
+
const eventValues: Array<typeof schema.sessionEvents.$inferInsert> = [];
|
|
156
|
+
const resolutions = pending.map((call) => {
|
|
157
|
+
const resultType = TOOL_RESULT_TYPE_BY_CALL_TYPE[call.callType];
|
|
158
|
+
const existingCall = history.find(
|
|
159
|
+
({ item }) => historyItemType(item) === call.callType && historyCallId(item) === call.callId,
|
|
160
|
+
);
|
|
161
|
+
const existingResult = resultType
|
|
162
|
+
? history.find(
|
|
163
|
+
({ item }) => historyItemType(item) === resultType && historyCallId(item) === call.callId,
|
|
164
|
+
)
|
|
165
|
+
: undefined;
|
|
166
|
+
const interruptedResult = interruptedToolCallResult({
|
|
167
|
+
callType: call.callType,
|
|
168
|
+
callId: call.callId,
|
|
169
|
+
callItem: call.callItem,
|
|
170
|
+
reason: input.reason,
|
|
171
|
+
});
|
|
172
|
+
return {
|
|
173
|
+
call,
|
|
174
|
+
existingCall,
|
|
175
|
+
existingResult,
|
|
176
|
+
rawCallIsValid: historyItemType(call.callItem) === call.callType,
|
|
177
|
+
result: existingResult?.item ?? call.resultItem ?? interruptedResult,
|
|
178
|
+
interrupted: !existingResult && !call.resultItem,
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
for (const resolution of resolutions) {
|
|
183
|
+
if (
|
|
184
|
+
!resolution.existingResult &&
|
|
185
|
+
!resolution.existingCall &&
|
|
186
|
+
resolution.result &&
|
|
187
|
+
resolution.rawCallIsValid
|
|
188
|
+
) {
|
|
189
|
+
historyValues.push({
|
|
190
|
+
accountId: input.accountId,
|
|
191
|
+
workspaceId: input.workspaceId,
|
|
192
|
+
sessionId: input.sessionId,
|
|
193
|
+
turnId: input.turnId,
|
|
194
|
+
position: nextPosition++,
|
|
195
|
+
item: sanitizeModelPayload(resolution.call.callItem),
|
|
196
|
+
active: true,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const orderedResults = [...resolutions].sort(
|
|
201
|
+
(left, right) =>
|
|
202
|
+
(left.call.resultRecordedAt?.getTime() ?? Number.MAX_SAFE_INTEGER) -
|
|
203
|
+
(right.call.resultRecordedAt?.getTime() ?? Number.MAX_SAFE_INTEGER),
|
|
204
|
+
);
|
|
205
|
+
for (const resolution of orderedResults) {
|
|
206
|
+
if (!resolution.existingResult && resolution.result && resolution.rawCallIsValid) {
|
|
207
|
+
historyValues.push({
|
|
208
|
+
accountId: input.accountId,
|
|
209
|
+
workspaceId: input.workspaceId,
|
|
210
|
+
sessionId: input.sessionId,
|
|
211
|
+
turnId: input.turnId,
|
|
212
|
+
position: nextPosition++,
|
|
213
|
+
item: sanitizeModelPayload(resolution.result),
|
|
214
|
+
active: true,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
eventValues.push({
|
|
218
|
+
accountId: input.accountId,
|
|
219
|
+
workspaceId: input.workspaceId,
|
|
220
|
+
sessionId: input.sessionId,
|
|
221
|
+
sequence: ++sequence,
|
|
222
|
+
type: "agent.toolCall.output",
|
|
223
|
+
turnId: input.turnId,
|
|
224
|
+
turnGeneration: resolution.call.executionGeneration,
|
|
225
|
+
turnAttemptId: resolution.call.attemptId,
|
|
226
|
+
turnAssociation: "current",
|
|
227
|
+
payload: sanitizeEventPayload({
|
|
228
|
+
id: resolution.call.callId,
|
|
229
|
+
output: resolution.interrupted
|
|
230
|
+
? {
|
|
231
|
+
isError: true,
|
|
232
|
+
content: [
|
|
233
|
+
{
|
|
234
|
+
type: "text",
|
|
235
|
+
text: `Tool execution was interrupted by ${input.reason}; its side-effect outcome is unknown.`,
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
}
|
|
239
|
+
: ((resolution.existingResult?.item ?? resolution.call.resultItem)?.output ??
|
|
240
|
+
resolution.existingResult?.item ??
|
|
241
|
+
resolution.call.resultItem),
|
|
242
|
+
recovery: {
|
|
243
|
+
interrupted: resolution.interrupted,
|
|
244
|
+
outcome: resolution.interrupted ? "unknown" : "durable_result_found",
|
|
245
|
+
reason: input.reason,
|
|
246
|
+
unsupportedCallShape:
|
|
247
|
+
resolution.interrupted && (!resolution.result || !resolution.rawCallIsValid),
|
|
248
|
+
},
|
|
249
|
+
}),
|
|
250
|
+
occurredAt: input.now,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (historyValues.length > 0) await tx.insert(schema.sessionHistoryItems).values(historyValues);
|
|
255
|
+
const inserted =
|
|
256
|
+
eventValues.length > 0
|
|
257
|
+
? await tx.insert(schema.sessionEvents).values(eventValues).returning()
|
|
258
|
+
: [];
|
|
259
|
+
await tx
|
|
260
|
+
.delete(schema.sessionPendingToolCalls)
|
|
261
|
+
.where(
|
|
262
|
+
and(
|
|
263
|
+
eq(schema.sessionPendingToolCalls.workspaceId, input.workspaceId),
|
|
264
|
+
eq(schema.sessionPendingToolCalls.sessionId, input.sessionId),
|
|
265
|
+
eq(schema.sessionPendingToolCalls.turnId, input.turnId),
|
|
266
|
+
),
|
|
267
|
+
);
|
|
268
|
+
return { sequence, events: inserted.map(mapEvent), closed: pending.length };
|
|
269
|
+
}
|