@opengeni/worker-bundle 1.2.1-canary.37222965366001 → 1.4.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/activities/agent-turn/errors.d.ts +1 -13
- package/dist/activities/agent-turn/subscription-lease.d.ts +1 -0
- package/dist/activities/agent-turn/tool-policy.d.ts +10 -1
- package/dist/activities/agent-turn/web-search.d.ts +33 -0
- package/dist/activities/codemode-dispatcher.d.ts +6 -2
- package/dist/activities/open-suffix-resume.d.ts +2 -0
- package/dist/activities/programmatic-approvals.d.ts +27 -0
- package/dist/activities/run-input.d.ts +2 -0
- package/dist/{activities-control-QSDPXLLD.js → activities-control-D5KVEPHI.js} +3 -3
- package/dist/{activities-turn-BMY3CDKL.js → activities-turn-4Z3M7Z3F.js} +554 -113
- package/dist/activities-turn-4Z3M7Z3F.js.map +1 -0
- package/dist/{chunk-JKMG5ZHH.js → chunk-BMTWULF7.js} +115 -31
- package/dist/chunk-BMTWULF7.js.map +1 -0
- package/dist/{chunk-XONQEVAD.js → chunk-FF4HPY7L.js} +1 -1
- package/dist/chunk-FF4HPY7L.js.map +1 -0
- package/dist/index.js +2 -2
- package/dist/observability-metrics.d.ts +1 -1
- package/package.json +21 -21
- package/src/activities/agent-turn/agent-build.ts +6 -4
- package/src/activities/agent-turn/errors.ts +82 -3
- package/src/activities/agent-turn/governance-model.ts +8 -8
- package/src/activities/agent-turn/sandbox-runtime.ts +16 -0
- package/src/activities/agent-turn/stream-attempt.ts +100 -21
- package/src/activities/agent-turn/subscription-lease.ts +10 -1
- package/src/activities/agent-turn/tool-environment.ts +20 -0
- package/src/activities/agent-turn/tool-policy.ts +26 -5
- package/src/activities/agent-turn/web-search.ts +252 -0
- package/src/activities/codemode-dispatcher.ts +145 -7
- package/src/activities/connector-attachments.ts +3 -1
- package/src/activities/open-suffix-resume.ts +107 -86
- package/src/activities/programmatic-approvals.ts +52 -0
- package/src/activities/run-input.ts +4 -1
- package/src/activity-services.ts +8 -0
- package/src/observability-metrics.ts +1 -0
- package/src/sandbox-routing.ts +58 -24
- package/dist/activities-turn-BMY3CDKL.js.map +0 -1
- package/dist/chunk-JKMG5ZHH.js.map +0 -1
- package/dist/chunk-XONQEVAD.js.map +0 -1
- /package/dist/{activities-control-QSDPXLLD.js.map → activities-control-D5KVEPHI.js.map} +0 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import type { AttemptToolDefinition } from "@opengeni/codemode";
|
|
2
|
+
import {
|
|
3
|
+
WEB_FETCH_TOOL_NAME,
|
|
4
|
+
WEB_SEARCH_TOOL_NAME,
|
|
5
|
+
webSearchCallPricing,
|
|
6
|
+
webSearchCreditMicros,
|
|
7
|
+
webSearchProviderConfig,
|
|
8
|
+
webSearchToolPlan,
|
|
9
|
+
type WebSearchProviderConfig,
|
|
10
|
+
type WebSearchProviderToolName,
|
|
11
|
+
type WebSearchSettings,
|
|
12
|
+
} from "@opengeni/config";
|
|
13
|
+
import {
|
|
14
|
+
WebSearchBillingRefusedError,
|
|
15
|
+
type WebSearchBilling,
|
|
16
|
+
type WebSearchCallCost,
|
|
17
|
+
type WebSearchCallScope,
|
|
18
|
+
} from "@opengeni/core";
|
|
19
|
+
import type { Observability } from "@opengeni/observability";
|
|
20
|
+
import {
|
|
21
|
+
WEB_FETCH_TOOL_DESCRIPTION,
|
|
22
|
+
WEB_FETCH_RETAINED_MAX_CHARS,
|
|
23
|
+
WEB_SEARCH_TOOL_DESCRIPTION,
|
|
24
|
+
WebSearchProviderError,
|
|
25
|
+
WebToolArgumentError,
|
|
26
|
+
createWebFetchProvider,
|
|
27
|
+
createWebSearchProvider,
|
|
28
|
+
parseWebFetchArguments,
|
|
29
|
+
parseWebSearchArguments,
|
|
30
|
+
renderWebPageWindow,
|
|
31
|
+
renderWebSearchResults,
|
|
32
|
+
webFetchInputSchema,
|
|
33
|
+
webSearchInputSchema,
|
|
34
|
+
type WebPage,
|
|
35
|
+
} from "@opengeni/runtime/web-search";
|
|
36
|
+
import { hostedWebSearchForTurn } from "./tool-policy";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Which web search one turn gets: the provider's hosted tool, Opengeni's
|
|
40
|
+
* provider tools, or neither. Tool preparation and agent construction both
|
|
41
|
+
* call this with the same accepted model and settings, so they agree.
|
|
42
|
+
*/
|
|
43
|
+
export function turnWebSearchPlan(
|
|
44
|
+
resolvedModel: { configured: { hostedWebSearch: boolean }; provider: { kind?: string } } | null,
|
|
45
|
+
settings: WebSearchSettings,
|
|
46
|
+
) {
|
|
47
|
+
return webSearchToolPlan(settings, {
|
|
48
|
+
hostedWebSearch: hostedWebSearchForTurn(resolvedModel, settings.webSearchEnabled),
|
|
49
|
+
// SuperGrok's native search is added by its transport, not as an agent
|
|
50
|
+
// tool, so provider tools would only duplicate it.
|
|
51
|
+
transportHostedSearch: resolvedModel?.provider.kind === "xai-subscription",
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Pages kept per attempt so paging through one page is not billed again. */
|
|
56
|
+
const PAGE_CACHE_ENTRIES = 16;
|
|
57
|
+
|
|
58
|
+
function textResult(text: string, isError: boolean) {
|
|
59
|
+
return { isError, content: [{ type: "text" as const, text }] };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type Pricing = ReturnType<typeof webSearchCallPricing>;
|
|
63
|
+
|
|
64
|
+
function settledCost(
|
|
65
|
+
operationId: string,
|
|
66
|
+
operation: "search" | "fetch",
|
|
67
|
+
provider: string,
|
|
68
|
+
pricing: Pricing,
|
|
69
|
+
reportedCostMicros: number | undefined,
|
|
70
|
+
): WebSearchCallCost {
|
|
71
|
+
// An explicit operator price wins; then the provider's own reported cost;
|
|
72
|
+
// then the built-in list price.
|
|
73
|
+
const basis: WebSearchCallCost["basis"] = pricing.explicit
|
|
74
|
+
? "configured_price"
|
|
75
|
+
: reportedCostMicros !== undefined
|
|
76
|
+
? "provider_reported"
|
|
77
|
+
: "list_price";
|
|
78
|
+
const providerMicros =
|
|
79
|
+
basis === "provider_reported" ? reportedCostMicros! : pricing.providerMicros;
|
|
80
|
+
return {
|
|
81
|
+
operationId,
|
|
82
|
+
operation,
|
|
83
|
+
provider,
|
|
84
|
+
providerMicros,
|
|
85
|
+
creditMicros: webSearchCreditMicros(providerMicros, pricing.marginBps),
|
|
86
|
+
marginBps: pricing.marginBps,
|
|
87
|
+
basis,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function providerFailure(error: WebSearchProviderError): string {
|
|
92
|
+
return `${error.message}${error.retryable ? ". You may retry shortly." : "."}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The `web_search` and `web_fetch` attempt tools for one turn. The caller
|
|
97
|
+
* decides which names the turn gets ({@link webSearchToolPlan}); this only
|
|
98
|
+
* builds them. Provider keys stay in the worker and never reach a sandbox.
|
|
99
|
+
*/
|
|
100
|
+
export function webSearchToolDefinitions(input: {
|
|
101
|
+
settings: WebSearchSettings;
|
|
102
|
+
tools: readonly WebSearchProviderToolName[];
|
|
103
|
+
scope: WebSearchCallScope;
|
|
104
|
+
billing: WebSearchBilling;
|
|
105
|
+
observability: Pick<Observability, "warn">;
|
|
106
|
+
fetch?: typeof fetch;
|
|
107
|
+
}): AttemptToolDefinition[] {
|
|
108
|
+
const config: WebSearchProviderConfig | null = webSearchProviderConfig(input.settings);
|
|
109
|
+
if (!config || input.tools.length === 0) return [];
|
|
110
|
+
const adapterInput = {
|
|
111
|
+
timeoutMs: config.timeoutMs,
|
|
112
|
+
...(input.fetch ? { fetch: input.fetch } : {}),
|
|
113
|
+
};
|
|
114
|
+
const settle = async (cost: WebSearchCallCost) => {
|
|
115
|
+
try {
|
|
116
|
+
await input.billing.settle(input.scope, cost);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
// The provider already answered. Keep the model's result; the missing
|
|
119
|
+
// receipt is an operator-visible accounting fault, never a tool failure.
|
|
120
|
+
input.observability.warn("web search usage settlement failed", {
|
|
121
|
+
operation: cost.operation,
|
|
122
|
+
provider: cost.provider,
|
|
123
|
+
error: error instanceof Error ? error.message : String(error),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const definitions: AttemptToolDefinition[] = [];
|
|
128
|
+
|
|
129
|
+
if (input.tools.includes(WEB_SEARCH_TOOL_NAME)) {
|
|
130
|
+
const provider = createWebSearchProvider({ ...adapterInput, endpoint: config.search });
|
|
131
|
+
const pricing = webSearchCallPricing(config, "search");
|
|
132
|
+
definitions.push({
|
|
133
|
+
identity: { serverId: "opengeni", toolName: WEB_SEARCH_TOOL_NAME },
|
|
134
|
+
modelName: WEB_SEARCH_TOOL_NAME,
|
|
135
|
+
codemodePath: ["opengeni", WEB_SEARCH_TOOL_NAME],
|
|
136
|
+
title: "Search the web",
|
|
137
|
+
description: WEB_SEARCH_TOOL_DESCRIPTION,
|
|
138
|
+
inputSchema: webSearchInputSchema as unknown as AttemptToolDefinition["inputSchema"],
|
|
139
|
+
annotations: {
|
|
140
|
+
title: "Search the web",
|
|
141
|
+
readOnlyHint: true,
|
|
142
|
+
destructiveHint: false,
|
|
143
|
+
idempotentHint: true,
|
|
144
|
+
openWorldHint: true,
|
|
145
|
+
},
|
|
146
|
+
source: "opengeni",
|
|
147
|
+
approval: "none",
|
|
148
|
+
execute: async (args, context) => {
|
|
149
|
+
let request: ReturnType<typeof parseWebSearchArguments>;
|
|
150
|
+
try {
|
|
151
|
+
request = parseWebSearchArguments(args);
|
|
152
|
+
await input.billing.admit(input.scope, pricing.providerMicros);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (
|
|
155
|
+
error instanceof WebToolArgumentError ||
|
|
156
|
+
error instanceof WebSearchBillingRefusedError
|
|
157
|
+
)
|
|
158
|
+
return textResult(error.message, true);
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
const response = await provider.search(request, { signal: context.signal });
|
|
163
|
+
await settle(
|
|
164
|
+
settledCost(
|
|
165
|
+
context.operationId,
|
|
166
|
+
"search",
|
|
167
|
+
config.search.provider,
|
|
168
|
+
pricing,
|
|
169
|
+
response.reportedCostMicros,
|
|
170
|
+
),
|
|
171
|
+
);
|
|
172
|
+
return textResult(renderWebSearchResults(request.query, response.results), false);
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if (context.signal?.aborted) throw error;
|
|
175
|
+
if (error instanceof WebSearchProviderError) {
|
|
176
|
+
return textResult(`Web search failed: ${providerFailure(error)}`, true);
|
|
177
|
+
}
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (input.tools.includes(WEB_FETCH_TOOL_NAME) && config.fetch) {
|
|
185
|
+
const fetchEndpoint = config.fetch;
|
|
186
|
+
const provider = createWebFetchProvider({ ...adapterInput, endpoint: fetchEndpoint });
|
|
187
|
+
const pricing = webSearchCallPricing(config, "fetch");
|
|
188
|
+
const pages = new Map<string, WebPage>();
|
|
189
|
+
definitions.push({
|
|
190
|
+
identity: { serverId: "opengeni", toolName: WEB_FETCH_TOOL_NAME },
|
|
191
|
+
modelName: WEB_FETCH_TOOL_NAME,
|
|
192
|
+
codemodePath: ["opengeni", WEB_FETCH_TOOL_NAME],
|
|
193
|
+
title: "Read a web page",
|
|
194
|
+
description: WEB_FETCH_TOOL_DESCRIPTION,
|
|
195
|
+
inputSchema: webFetchInputSchema as unknown as AttemptToolDefinition["inputSchema"],
|
|
196
|
+
annotations: {
|
|
197
|
+
title: "Read a web page",
|
|
198
|
+
readOnlyHint: true,
|
|
199
|
+
destructiveHint: false,
|
|
200
|
+
idempotentHint: true,
|
|
201
|
+
openWorldHint: true,
|
|
202
|
+
},
|
|
203
|
+
source: "opengeni",
|
|
204
|
+
approval: "none",
|
|
205
|
+
execute: async (args, context) => {
|
|
206
|
+
let request: ReturnType<typeof parseWebFetchArguments>;
|
|
207
|
+
try {
|
|
208
|
+
request = parseWebFetchArguments(args);
|
|
209
|
+
} catch (error) {
|
|
210
|
+
if (error instanceof WebToolArgumentError) return textResult(error.message, true);
|
|
211
|
+
throw error;
|
|
212
|
+
}
|
|
213
|
+
const cached = pages.get(request.url);
|
|
214
|
+
if (cached) return textResult(renderWebPageWindow(cached, request), false);
|
|
215
|
+
try {
|
|
216
|
+
await input.billing.admit(input.scope, pricing.providerMicros);
|
|
217
|
+
} catch (error) {
|
|
218
|
+
if (error instanceof WebSearchBillingRefusedError) return textResult(error.message, true);
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
const page = await provider.fetch({ url: request.url }, { signal: context.signal });
|
|
223
|
+
await settle(
|
|
224
|
+
settledCost(
|
|
225
|
+
context.operationId,
|
|
226
|
+
"fetch",
|
|
227
|
+
fetchEndpoint.provider,
|
|
228
|
+
pricing,
|
|
229
|
+
page.reportedCostMicros,
|
|
230
|
+
),
|
|
231
|
+
);
|
|
232
|
+
if (pages.size >= PAGE_CACHE_ENTRIES) {
|
|
233
|
+
pages.delete(pages.keys().next().value!);
|
|
234
|
+
}
|
|
235
|
+
// Bound per-attempt memory; one extra character keeps the cut visible.
|
|
236
|
+
pages.set(request.url, {
|
|
237
|
+
...page,
|
|
238
|
+
content: page.content.slice(0, WEB_FETCH_RETAINED_MAX_CHARS + 1),
|
|
239
|
+
});
|
|
240
|
+
return textResult(renderWebPageWindow(page, request), false);
|
|
241
|
+
} catch (error) {
|
|
242
|
+
if (context.signal?.aborted) throw error;
|
|
243
|
+
if (error instanceof WebSearchProviderError) {
|
|
244
|
+
return textResult(`Web fetch failed: ${providerFailure(error)}`, true);
|
|
245
|
+
}
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
return definitions;
|
|
252
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
bindMcpTelemetry,
|
|
4
|
+
measureMcpPhase,
|
|
5
|
+
withMcpCallIdentity,
|
|
6
|
+
recordToolApproval,
|
|
7
|
+
} from "@opengeni/observability";
|
|
3
8
|
import {
|
|
4
9
|
AttemptToolApprovalRequiredError,
|
|
5
10
|
AttemptToolCatalogStaleError,
|
|
@@ -15,13 +20,18 @@ import {
|
|
|
15
20
|
CODEMODE_CLAIM_LEASE_MS,
|
|
16
21
|
CODEMODE_CLAIM_HEARTBEAT_MS,
|
|
17
22
|
CODEMODE_MAX_CONCURRENT_CALLS_PER_ATTEMPT,
|
|
18
|
-
|
|
23
|
+
CodemodeOperation,
|
|
19
24
|
type SessionEvent,
|
|
20
25
|
type ToolDisplayMetadata,
|
|
21
26
|
type ToolFamily,
|
|
22
27
|
} from "@opengeni/contracts";
|
|
23
28
|
import {
|
|
24
29
|
cancelQueuedCodemodeOperationsForAttempt,
|
|
30
|
+
waitForCodemodeApproval,
|
|
31
|
+
bindCodemodePreparation,
|
|
32
|
+
listTurnCodemodeApprovals,
|
|
33
|
+
adoptCodemodeApprovalWithOutput,
|
|
34
|
+
readTurnCodemodeOperation,
|
|
25
35
|
claimCodemodeOperation,
|
|
26
36
|
failCodemodeOperation,
|
|
27
37
|
getSessionEventByClientEventId,
|
|
@@ -76,6 +86,7 @@ export class CodemodeAttemptDispatcher {
|
|
|
76
86
|
private readonly toolDisplayMetadata?: (modelName: string) => ToolDisplayMetadata | undefined,
|
|
77
87
|
/** Content-free analytics family for a catalog entry; event enrichment only. */
|
|
78
88
|
private readonly toolFamily?: (entry: AttemptToolCatalogEntry) => ToolFamily | null,
|
|
89
|
+
private readonly approvalWait?: { beginWait(): (accepted: boolean) => void },
|
|
79
90
|
) {
|
|
80
91
|
if (
|
|
81
92
|
environment.catalog.accountId !== scope.accountId ||
|
|
@@ -179,6 +190,94 @@ export class CodemodeAttemptDispatcher {
|
|
|
179
190
|
);
|
|
180
191
|
}
|
|
181
192
|
|
|
193
|
+
async resumeApproved(
|
|
194
|
+
callerSubjectId: string,
|
|
195
|
+
decisionOperationId?: string,
|
|
196
|
+
): Promise<CodemodeOperation[]> {
|
|
197
|
+
const operations = await listTurnCodemodeApprovals(this.db, this.scope);
|
|
198
|
+
if (
|
|
199
|
+
decisionOperationId &&
|
|
200
|
+
// Model-tool approval IDs are opaque strings. Only programmatic UUIDs
|
|
201
|
+
// can address the Codemode operation journal.
|
|
202
|
+
CodemodeOperation.shape.operationId.safeParse(decisionOperationId).success &&
|
|
203
|
+
!operations.some((operation) => operation.operationId === decisionOperationId)
|
|
204
|
+
) {
|
|
205
|
+
const decided = await readTurnCodemodeOperation(this.db, {
|
|
206
|
+
...this.scope,
|
|
207
|
+
callerSubjectId,
|
|
208
|
+
operationId: decisionOperationId,
|
|
209
|
+
});
|
|
210
|
+
if (decided) operations.push(decided);
|
|
211
|
+
}
|
|
212
|
+
for (const operation of operations) {
|
|
213
|
+
if (!["waiting_for_approval", "queued", "running"].includes(operation.state)) continue;
|
|
214
|
+
let effectDigest: string | null = null;
|
|
215
|
+
try {
|
|
216
|
+
effectDigest = this.environment.effectDigest(operation.identity);
|
|
217
|
+
} catch {
|
|
218
|
+
/* Removed tool: stale. */
|
|
219
|
+
}
|
|
220
|
+
const continuation = await adoptCodemodeApprovalWithOutput(this.db, {
|
|
221
|
+
...this.scope,
|
|
222
|
+
operationId: operation.operationId,
|
|
223
|
+
callerSubjectId,
|
|
224
|
+
catalogDigest: this.environment.catalog.digest,
|
|
225
|
+
effectDigest,
|
|
226
|
+
});
|
|
227
|
+
await publishDurableSessionEvents(
|
|
228
|
+
this.bus,
|
|
229
|
+
this.scope.workspaceId,
|
|
230
|
+
this.scope.sessionId,
|
|
231
|
+
continuation.events,
|
|
232
|
+
);
|
|
233
|
+
const adopted = continuation.operation;
|
|
234
|
+
if (adopted && adopted.state !== operation.state) {
|
|
235
|
+
const outcome =
|
|
236
|
+
adopted.state === "queued"
|
|
237
|
+
? "resumed"
|
|
238
|
+
: adopted.state === "outcome_unknown"
|
|
239
|
+
? "unknown"
|
|
240
|
+
: adopted.errorCode === "approval_stale"
|
|
241
|
+
? "stale"
|
|
242
|
+
: "rejected";
|
|
243
|
+
recordToolApproval(
|
|
244
|
+
outcome,
|
|
245
|
+
"continuation",
|
|
246
|
+
operation.state === "waiting_for_approval"
|
|
247
|
+
? Math.max(0, Date.now() - Date.parse(operation.updatedAt))
|
|
248
|
+
: undefined,
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
if (adopted?.state !== "queued") continue;
|
|
252
|
+
const claim = await claimCodemodeOperation(this.db, {
|
|
253
|
+
...this.scope,
|
|
254
|
+
operationId: adopted.operationId,
|
|
255
|
+
claimId: randomUUID(),
|
|
256
|
+
catalogDigest: this.environment.catalog.digest,
|
|
257
|
+
claimLeaseMs: this.claimLeaseMs,
|
|
258
|
+
});
|
|
259
|
+
if (claim.status === "claimed") {
|
|
260
|
+
const execution = this.execute(claim.operation, claim.claimId, true);
|
|
261
|
+
this.inFlight.set(adopted.operationId, execution);
|
|
262
|
+
try {
|
|
263
|
+
await execution;
|
|
264
|
+
} finally {
|
|
265
|
+
this.inFlight.delete(adopted.operationId);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const receipts: CodemodeOperation[] = [];
|
|
270
|
+
for (const operation of operations) {
|
|
271
|
+
const receipt = await readTurnCodemodeOperation(this.db, {
|
|
272
|
+
...this.scope,
|
|
273
|
+
callerSubjectId,
|
|
274
|
+
operationId: operation.operationId,
|
|
275
|
+
});
|
|
276
|
+
if (receipt) receipts.push(receipt);
|
|
277
|
+
}
|
|
278
|
+
return receipts;
|
|
279
|
+
}
|
|
280
|
+
|
|
182
281
|
async close(reason = "Execution attempt ended"): Promise<void> {
|
|
183
282
|
if (this.closing) {
|
|
184
283
|
await Promise.allSettled([...this.inFlight.values()]);
|
|
@@ -277,18 +376,56 @@ export class CodemodeAttemptDispatcher {
|
|
|
277
376
|
}
|
|
278
377
|
|
|
279
378
|
let preparedCall: Awaited<ReturnType<AttemptToolEnvironment["prepareCall"]>>;
|
|
379
|
+
let releaseWait: ((accepted: boolean) => void) | undefined;
|
|
280
380
|
try {
|
|
381
|
+
if (operation.durableApproval) {
|
|
382
|
+
if (!this.approvalWait) throw new Error("Durable approval yield is unavailable");
|
|
383
|
+
releaseWait = this.approvalWait.beginWait();
|
|
384
|
+
if (
|
|
385
|
+
!(await bindCodemodePreparation(this.db, {
|
|
386
|
+
...this.scope,
|
|
387
|
+
operationId: operation.operationId,
|
|
388
|
+
claimId,
|
|
389
|
+
effectDigest: this.environment.effectDigest(operation.identity),
|
|
390
|
+
}))
|
|
391
|
+
)
|
|
392
|
+
throw new Error("Operation preparation is stale");
|
|
393
|
+
}
|
|
281
394
|
preparedCall = await this.environment.prepareCall(
|
|
282
395
|
{
|
|
283
396
|
operationId: operation.operationId,
|
|
284
|
-
catalogDigest:
|
|
397
|
+
catalogDigest: this.environment.catalog.digest,
|
|
285
398
|
identity: operation.identity,
|
|
286
399
|
arguments: operation.arguments,
|
|
287
|
-
caller:
|
|
400
|
+
caller: {
|
|
401
|
+
kind: "codemode",
|
|
402
|
+
subjectId:
|
|
403
|
+
operation.caller.subjectId === `sandbox:${operation.attemptId}`
|
|
404
|
+
? `sandbox:${this.scope.attemptId}`
|
|
405
|
+
: operation.caller.subjectId,
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
signal,
|
|
410
|
+
...(operation.durableApproval ? { transportMeta: { durableApproval: true } } : {}),
|
|
288
411
|
},
|
|
289
|
-
{ signal },
|
|
290
412
|
);
|
|
413
|
+
if (preparedCall.waitingForApproval) {
|
|
414
|
+
const waiting = await waitForCodemodeApproval(this.db, {
|
|
415
|
+
...this.scope,
|
|
416
|
+
operationId: operation.operationId,
|
|
417
|
+
claimId,
|
|
418
|
+
...preparedCall.waitingForApproval,
|
|
419
|
+
effectDigest: preparedCall.effectDigest!,
|
|
420
|
+
});
|
|
421
|
+
if (!waiting) throw new Error("Approval could not be bound to the operation");
|
|
422
|
+
recordToolApproval("waiting");
|
|
423
|
+
releaseWait?.(true);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
releaseWait?.(false);
|
|
291
427
|
} catch (error) {
|
|
428
|
+
releaseWait?.(false);
|
|
292
429
|
await this.settleWithOutput(operation, claimId, {
|
|
293
430
|
state: "failed",
|
|
294
431
|
errorCode: errorCode(error, signal, false),
|
|
@@ -448,6 +585,7 @@ export class CodemodeAttemptDispatcher {
|
|
|
448
585
|
settlement,
|
|
449
586
|
});
|
|
450
587
|
if (!result.committed || result.events.length === 0) return;
|
|
588
|
+
recordToolApproval(settlement.state === "outcome_unknown" ? "unknown" : settlement.state);
|
|
451
589
|
await publishDurableSessionEvents(
|
|
452
590
|
this.bus,
|
|
453
591
|
this.scope.workspaceId,
|
|
@@ -490,8 +628,8 @@ function assertMatchingCodemodeToolCallCreated(
|
|
|
490
628
|
if (
|
|
491
629
|
event.type !== "agent.toolCall.created" ||
|
|
492
630
|
event.turnId !== scope.turnId ||
|
|
493
|
-
event.turnGeneration !==
|
|
494
|
-
event.turnAttemptId !==
|
|
631
|
+
event.turnGeneration !== operation.executionGeneration ||
|
|
632
|
+
event.turnAttemptId !== operation.attemptId ||
|
|
495
633
|
event.turnAssociation !== "current" ||
|
|
496
634
|
payload?.id !== operation.operationId ||
|
|
497
635
|
payload?.name !== entry.modelName ||
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import {
|
|
13
13
|
ChannelAPartialMutationError,
|
|
14
14
|
RoutingMutationOutcomeUnknownError,
|
|
15
|
+
isRoutingMutationOutputRejectedError,
|
|
15
16
|
SandboxChannelAService,
|
|
16
17
|
} from "@opengeni/runtime/sandbox";
|
|
17
18
|
|
|
@@ -159,7 +160,8 @@ export async function materializeConnectorAttachmentsInChannel(
|
|
|
159
160
|
} catch (error) {
|
|
160
161
|
if (
|
|
161
162
|
error instanceof ChannelAPartialMutationError ||
|
|
162
|
-
error instanceof RoutingMutationOutcomeUnknownError
|
|
163
|
+
error instanceof RoutingMutationOutcomeUnknownError ||
|
|
164
|
+
isRoutingMutationOutputRejectedError(error)
|
|
163
165
|
) {
|
|
164
166
|
throw error;
|
|
165
167
|
}
|