@gea-ai/agent-sdk 0.1.260925-alpha.1 → 0.1.261003-alpha.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.
@@ -7,6 +7,9 @@ let compiled;
7
7
  export function agentCore(options = {}) {
8
8
  return {
9
9
  ...options,
10
+ // A silent model stream must not retain a Run until the provider hard deadline.
11
+ // Explicit timeout options (including {}) retain caller-owned policy.
12
+ timeout: options.timeout ?? { chunkMs: 5 * 60_000 },
10
13
  output: options.output === undefined
11
14
  ? undefined
12
15
  : toAgentCoreOutput(options.output),
@@ -0,0 +1,3 @@
1
+ /** Old Core policy denials lacked the rejected approval required by AI SDK.
2
+ * Repair only their presentation; persisted Core decisions still own execution. */
3
+ export declare function repairLegacyToolDenials(message: unknown): unknown;
@@ -0,0 +1,38 @@
1
+ import { z } from "zod";
2
+ const assistantHistorySchema = z
3
+ .object({
4
+ role: z.literal("assistant"),
5
+ parts: z.array(z.unknown()),
6
+ })
7
+ .passthrough();
8
+ const legacyDenialSchema = z
9
+ .object({
10
+ type: z.union([z.literal("dynamic-tool"), z.string().startsWith("tool-")]),
11
+ toolCallId: z.string(),
12
+ state: z.literal("output-denied"),
13
+ approval: z
14
+ .object({ id: z.string(), approved: z.literal(true) })
15
+ .passthrough()
16
+ .optional(),
17
+ })
18
+ .passthrough();
19
+ /** Old Core policy denials lacked the rejected approval required by AI SDK.
20
+ * Repair only their presentation; persisted Core decisions still own execution. */
21
+ export function repairLegacyToolDenials(message) {
22
+ const history = assistantHistorySchema.safeParse(message);
23
+ if (!history.success)
24
+ return message;
25
+ return {
26
+ ...history.data,
27
+ parts: history.data.parts.map((part) => {
28
+ const denied = legacyDenialSchema.safeParse(part);
29
+ return denied.success
30
+ ? {
31
+ ...denied.data,
32
+ state: "output-error",
33
+ errorText: "Tool execution was denied by policy.",
34
+ }
35
+ : part;
36
+ }),
37
+ };
38
+ }
@@ -57,60 +57,114 @@ export async function cancelRemoteRun(fetch, run) {
57
57
  const response = await fetch(`/runs/${run.id}/cancel`, { method: "POST" });
58
58
  await responseJson(response);
59
59
  }
60
+ class RemoteRunReadError extends TaggedError("RemoteRunReadError")() {
61
+ }
62
+ async function waitForRemoteRead(ms, signal) {
63
+ signal.throwIfAborted();
64
+ await new Promise((resolve, reject) => {
65
+ const abort = () => {
66
+ clearTimeout(timer);
67
+ reject(signal.reason);
68
+ };
69
+ const timer = setTimeout(() => {
70
+ signal.removeEventListener("abort", abort);
71
+ resolve();
72
+ }, ms);
73
+ signal.addEventListener("abort", abort, { once: true });
74
+ if (signal.aborted)
75
+ abort();
76
+ });
77
+ }
78
+ /** Only idempotent reads retry. Admission and Tool execution are never repeated here. */
79
+ async function readRemoteRunPage(fetch, path, signal) {
80
+ for (let attempt = 0;; attempt++) {
81
+ signal.throwIfAborted();
82
+ const response = await Result.tryPromise(() => fetch(path, { signal }));
83
+ signal.throwIfAborted();
84
+ let status;
85
+ let delay = 250 * 2 ** attempt;
86
+ if (response.isOk()) {
87
+ if (response.value.ok)
88
+ return response.value.json();
89
+ status = response.value.status;
90
+ const retryAfter = response.value.headers.get("retry-after");
91
+ if (retryAfter) {
92
+ const requestedDelay = /^\d+$/u.test(retryAfter)
93
+ ? Number(retryAfter) * 1000
94
+ : Date.parse(retryAfter) - Date.now();
95
+ if (Number.isFinite(requestedDelay))
96
+ delay = Math.max(delay, requestedDelay);
97
+ }
98
+ await response.value.body?.cancel();
99
+ }
100
+ if (attempt >= 2 ||
101
+ delay > 5000 ||
102
+ (status !== undefined && status !== 408 && status !== 429 && status < 500))
103
+ throw new RemoteRunReadError({
104
+ message: "Remote Run read failed.",
105
+ status,
106
+ });
107
+ await waitForRemoteRead(delay, signal);
108
+ }
109
+ }
60
110
  export async function observeRemoteRun(fetch, run, signal, onInputRequired) {
61
111
  let reportedInput;
62
- while (true) {
63
- signal.throwIfAborted();
64
- const record = agentHttpRunSchema.parse(await responseJson(await fetch(`/runs/${run.id}`, { signal })));
65
- if (record.id !== run.id || record.session_id !== run.sessionId)
66
- throw new Error("Remote Run identity mismatch.");
67
- const terminal = ["finished", "error", "aborted", "interrupted"].includes(record.status);
68
- const inputMessageId = record.status === "waiting" ? record.input_message_id : null;
69
- if (terminal || (onInputRequired && inputMessageId)) {
70
- const text = [];
71
- let cursor = null;
72
- do {
73
- const page = messagesSchema.parse(await responseJson(await fetch(`/sessions/${run.sessionId}/messages?limit=100${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""}`, { signal })));
74
- for (const message of page.items)
75
- if (message.run_id === run.id && message.role === "assistant") {
76
- if (!terminal && message.id === inputMessageId && onInputRequired) {
77
- const input = agentApplicationAssistantMessageSchema.parse({
78
- id: message.id,
79
- role: message.role,
80
- parts: message.parts,
81
- });
82
- const key = agentInputRequestKey(input);
83
- if (key !== reportedInput) {
84
- await onInputRequired(input);
85
- reportedInput = key;
112
+ try {
113
+ while (true) {
114
+ signal.throwIfAborted();
115
+ const record = agentHttpRunSchema.parse(await readRemoteRunPage(fetch, `/runs/${run.id}`, signal));
116
+ if (record.id !== run.id || record.session_id !== run.sessionId)
117
+ throw new Error("Remote Run identity mismatch.");
118
+ const terminal = ["finished", "error", "aborted", "interrupted"].includes(record.status);
119
+ const inputMessageId = record.status === "waiting" ? record.input_message_id : null;
120
+ if (terminal || (onInputRequired && inputMessageId)) {
121
+ const text = [];
122
+ let cursor = null;
123
+ do {
124
+ const page = messagesSchema.parse(await readRemoteRunPage(fetch, `/sessions/${run.sessionId}/messages?limit=100${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""}`, signal));
125
+ for (const message of page.items)
126
+ if (message.run_id === run.id && message.role === "assistant") {
127
+ if (!terminal &&
128
+ message.id === inputMessageId &&
129
+ onInputRequired) {
130
+ const input = agentApplicationAssistantMessageSchema.parse({
131
+ id: message.id,
132
+ role: message.role,
133
+ parts: message.parts,
134
+ });
135
+ const key = agentInputRequestKey(input);
136
+ if (key !== reportedInput) {
137
+ await onInputRequired(input);
138
+ reportedInput = key;
139
+ }
86
140
  }
141
+ for (const part of message.parts)
142
+ if (part.type === "text" && typeof part.text === "string")
143
+ text.push(part.text);
87
144
  }
88
- for (const part of message.parts)
89
- if (part.type === "text" && typeof part.text === "string")
90
- text.push(part.text);
91
- }
92
- cursor = page.next_cursor;
93
- } while (cursor);
94
- if (terminal)
95
- return {
96
- status: record.status,
97
- text: text.join("\n").slice(0, 200_000),
98
- };
145
+ cursor = page.next_cursor;
146
+ } while (cursor);
147
+ if (terminal)
148
+ return {
149
+ status: record.status,
150
+ text: text.join("\n").slice(0, 200_000),
151
+ };
152
+ }
153
+ if (!["queued", "running", "waiting"].includes(record.status))
154
+ throw new Error("Unknown remote Run status.");
155
+ await waitForRemoteRead(1000, signal);
99
156
  }
100
- if (!["queued", "running", "waiting"].includes(record.status))
101
- throw new Error("Unknown remote Run status.");
102
- await new Promise((resolve, reject) => {
103
- const abort = () => {
104
- clearTimeout(timer);
105
- reject(signal.reason);
106
- };
107
- const timer = setTimeout(() => {
108
- signal.removeEventListener("abort", abort);
109
- resolve();
110
- }, 1000);
111
- signal.addEventListener("abort", abort, { once: true });
112
- if (signal.aborted)
113
- abort();
114
- });
157
+ }
158
+ catch (error) {
159
+ // Stop only this parent-owned observation. The remote execution/directory is untouched.
160
+ // Host shutdown leaves the relation pending so another dispatcher can resume it.
161
+ signal.throwIfAborted();
162
+ const status = RemoteRunReadError.is(error) && error.status
163
+ ? ` (HTTP ${error.status})`
164
+ : "";
165
+ return {
166
+ status: "interrupted",
167
+ text: `Remote observation interrupted${status}; execution outcome is unknown. Run ${run.id}, Session ${run.sessionId}. Do not resubmit the task; inspect this existing Run.`,
168
+ };
115
169
  }
116
170
  }
@@ -20,7 +20,7 @@ export declare class AgentRunHost {
20
20
  response: Response;
21
21
  dispose?: () => void;
22
22
  }>;
23
- cancelChild(run: AgentChildRun): Promise<void>;
23
+ cancelChild(run: AgentChildRun): Promise<boolean | void>;
24
24
  onError(error: unknown, identity?: {
25
25
  runId?: string;
26
26
  sessionId?: string;
@@ -161,9 +161,7 @@ export class AgentRunHost {
161
161
  dispatch(run, inboxExecution) {
162
162
  if (this.closed)
163
163
  throw new Error("Run dispatcher is stopping.");
164
- if (this.executions.has(run.id) ||
165
- run.outcome ||
166
- (run.cancelRequested && run.destination.type !== "remote"))
164
+ if (this.executions.has(run.id) || run.outcome)
167
165
  return;
168
166
  const cancellation = new AbortController();
169
167
  this.executions.set(run.id, cancellation);
@@ -207,8 +205,18 @@ export class AgentRunHost {
207
205
  });
208
206
  }
209
207
  else {
210
- cancellation.abort();
211
- return;
208
+ // Keep admission alive until the Session has received cancellation.
209
+ // Aborting the request earlier can strand an uninitialized child.
210
+ const cancelled = await Result.tryPromise(() => this.host.cancelChild(run));
211
+ if (cancelled.isOk() && cancelled.value !== false) {
212
+ cancellation.abort();
213
+ return;
214
+ }
215
+ if (cancelled.isErr())
216
+ this.host.onError(cancelled.error, {
217
+ runId: run.id,
218
+ sessionId: run.sessionId,
219
+ });
212
220
  }
213
221
  }
214
222
  timer = setTimeout(() => {
@@ -284,8 +292,9 @@ export class AgentRunHost {
284
292
  this.dispatch(run);
285
293
  }
286
294
  else {
287
- this.executions.get(runId)?.abort();
288
- await this.host.cancelChild(run);
295
+ this.dispatch(run);
296
+ if ((await this.host.cancelChild(run)) !== false)
297
+ this.executions.get(runId)?.abort();
289
298
  }
290
299
  }
291
300
  return Response.json({
@@ -444,6 +444,15 @@ export async function handleAgentSessionExecutionRequest(request, storage) {
444
444
  handlerMs = performance.now() - handlerStartedAt;
445
445
  return response;
446
446
  });
447
+ console.info("AgentSession lease release completed", {
448
+ event: "agent.session.lease_released",
449
+ runId: input.runId,
450
+ attemptId: input.attemptId,
451
+ executionId: input.inbox.executionId,
452
+ inputWait: Boolean(input.inbox.inputWait),
453
+ status: response.status,
454
+ elapsedMs: Math.round(performance.now() - startedAt),
455
+ });
447
456
  response.headers.set("server-timing", `session-handler;dur=${handlerMs.toFixed(1)}, session-transaction;dur=${(performance.now() - startedAt).toFixed(1)}`);
448
457
  return response;
449
458
  }
@@ -683,7 +692,15 @@ export async function handleAgentSessionExecutionRequest(request, storage) {
683
692
  .parse(await readBody(request, storage));
684
693
  const result = await storage.transaction(async (tx) => {
685
694
  const active = await tx.get(key("active-turn"));
686
- if (!active || active.runId !== input.runId)
695
+ if (!active) {
696
+ // A local logical Run can execute or wait without a managed lease. The
697
+ // inbox still owns its descendants and fences cancellation by Run ID.
698
+ const state = await getAgentSessionRunState(tx, input.runId);
699
+ return {
700
+ requested: state?.status === "waiting" || state?.status === "running",
701
+ };
702
+ }
703
+ if (active.runId !== input.runId)
687
704
  return { requested: false };
688
705
  if (!active.cancelRequestedAt) {
689
706
  const turn = {
@@ -467,6 +467,20 @@ async function finishInbox(tx, state, runs, input) {
467
467
  await save(tx, state, runs);
468
468
  return Response.json({ error: "execution_not_expired" }, { status: 409 });
469
469
  }
470
+ console.warn("AgentSession execution lease expired", {
471
+ event: "agent.session.lease_expired",
472
+ sessionId: state.identity.sessionId,
473
+ runId: turn.runId,
474
+ attemptId: turn.attemptId,
475
+ requestId: turn.requestId,
476
+ executionId: state.execution.id,
477
+ leaseOwner: turn.leaseOwner,
478
+ leaseExpiresAt: turn.leaseExpiresAt,
479
+ started: state.started,
480
+ waiting: state.wait !== null,
481
+ pendingChildren: runs.filter((run) => !run.outcome).length,
482
+ pendingStarts: state.pendingStarts.length,
483
+ });
470
484
  }
471
485
  if (inputWait && !outcome && !state.stopped) {
472
486
  state.inputWait = {
@@ -135,6 +135,7 @@ export type AgentToolRuntimeDefinition = {
135
135
  input: unknown;
136
136
  sdk: AgentToolRuntimeSdk;
137
137
  }): Promise<unknown>;
138
+ scriptRuntime?: ReturnType<typeof import("./script-tools").getScriptToolRuntime>;
138
139
  inputSchema: z.ZodType;
139
140
  toModelOutput?: import("./index").DefinedCustomTool["toModelOutput"];
140
141
  }>;
@@ -1,6 +1,10 @@
1
+ import { AGENT_WORKER_TOOL_METADATA_HEADER, AGENT_WORKER_TOOL_METADATA_VERSION, } from "@gea-ai/contract/agent-worker-tools";
2
+ import { isAgentConnectorExposed } from "@gea-ai/contract/agent-packages";
3
+ import { createCodeMode } from "./code-mode.js";
1
4
  import { AGENT_SUBAGENT_CONCURRENCY_HEADER } from "@gea-ai/contract/agent-worker-tools";
2
5
  import { DEFAULT_MAX_CONCURRENT_SUBAGENTS } from "@gea-ai/contract/agent-packages";
3
6
  import { batchAgentStreamEvents, coalesceAgentTextDeltas, } from "./agent-stream-coalescing.js";
7
+ import { repairLegacyToolDenials } from "./agent-message-history.js";
4
8
  import { sendAgentSessionRequest } from "./agent-session-transport.js";
5
9
  import { agentSessionOverridesSchema } from "@gea-ai/contract/agent-session-overrides";
6
10
  import { resolveModelOptions } from "@gea-ai/contract/model-options";
@@ -184,7 +188,11 @@ async function runPreparedConnectorAuthorization(input, environment) {
184
188
  }
185
189
  const [identityValue, initializationValue] = await Promise.all([
186
190
  requireHostJson(environment.IDENTITY, "/context"),
187
- requireHostJson(environment.TOOLS, "/tools/initialize"),
191
+ requireHostJson(environment.TOOLS, "/tools/initialize", {
192
+ headers: {
193
+ [AGENT_WORKER_TOOL_METADATA_HEADER]: AGENT_WORKER_TOOL_METADATA_VERSION,
194
+ },
195
+ }),
188
196
  ]);
189
197
  const identity = agentToolIdentitySchema.parse(identityValue);
190
198
  const initialization = agentWorkerToolInitializationSchema.parse(initializationValue);
@@ -299,6 +307,7 @@ async function runPreparedAgent(runtime, agentKey, request, environment, startup
299
307
  "content-type": "application/json",
300
308
  "x-gea-agent-runs": "1",
301
309
  "x-gea-session-inbox": "1",
310
+ [AGENT_WORKER_TOOL_METADATA_HEADER]: AGENT_WORKER_TOOL_METADATA_VERSION,
302
311
  ...Object.fromEntries([...request.headers.entries()].filter(([name]) => ["traceparent", "tracestate", "x-gea-request-id"].includes(name))),
303
312
  },
304
313
  body: JSON.stringify({ metadata: parsed.data.metadata }),
@@ -2184,6 +2193,7 @@ function createHostBackedAgentTools(input) {
2184
2193
  execute: async (toolInput, options) => await invokeHostTool({
2185
2194
  host: input.host,
2186
2195
  messages: options.messages,
2196
+ signal: options.abortSignal,
2187
2197
  toolCallId: options.toolCallId,
2188
2198
  toolInput,
2189
2199
  toolName: definition.hostToolName ?? name,
@@ -2250,7 +2260,7 @@ function createHostBackedAgentTools(input) {
2250
2260
  continue;
2251
2261
  }
2252
2262
  const declaredOperation = grant.definition.operations.find((operation) => operation.name === descriptor.operation);
2253
- if (!grant.exposeToModel ||
2263
+ if (!isAgentConnectorExposed(grant, input.snapshot.codeMode) ||
2254
2264
  (grant.definition.runtime.type !== "mcp" && !declaredOperation)) {
2255
2265
  throw new Error("Agent Worker Connector operation is not granted.");
2256
2266
  }
@@ -2264,11 +2274,21 @@ function createHostBackedAgentTools(input) {
2264
2274
  .join("\n"),
2265
2275
  inputSchema: jsonSchema(descriptor.inputSchema),
2266
2276
  ...(grant.definition.runtime.type === "mcp"
2267
- ? { toModelOutput: mcpToolModelOutput }
2277
+ ? {
2278
+ toModelOutput: mcpToolModelOutput,
2279
+ ...(descriptor.outputSchema === undefined
2280
+ ? {}
2281
+ : {
2282
+ providerOptions: {
2283
+ gea: { mcpOutputSchema: descriptor.outputSchema },
2284
+ },
2285
+ }),
2286
+ }
2268
2287
  : {}),
2269
2288
  execute: async (toolInput, options) => await invokeHostTool({
2270
2289
  host: input.host,
2271
2290
  messages: options.messages,
2291
+ signal: options.abortSignal,
2272
2292
  toolCallId: options.toolCallId,
2273
2293
  toolInput,
2274
2294
  toolName,
@@ -3089,7 +3109,10 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
3089
3109
  !!Object.keys(input.snapshot.agentReferences ?? {}).length ||
3090
3110
  requiresToolInitialization(input.snapshot, input.hookRuntime)
3091
3111
  ? startupTracing.withSpan("agent.startup.tools", () => requireHostJson(host, "/tools/initialize", {
3092
- headers: { "x-gea-agent-runs": "1" },
3112
+ headers: {
3113
+ "x-gea-agent-runs": "1",
3114
+ [AGENT_WORKER_TOOL_METADATA_HEADER]: AGENT_WORKER_TOOL_METADATA_VERSION,
3115
+ },
3093
3116
  }).then((value) => agentWorkerToolInitializationSchema.parse(value)))
3094
3117
  : Promise.resolve(agentWorkerToolInitializationSchema.parse({
3095
3118
  connectors: [],
@@ -3146,13 +3169,19 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
3146
3169
  const submittedUser = payload.currentTurn.some((message) => message.role === "user")
3147
3170
  ? inputUser
3148
3171
  : undefined;
3149
- if (input.inboxExecution && submittedUser)
3150
- await appendSessionValue({
3151
- body: { message: submittedUser, runId: payload.runId },
3152
- chatId: payload.chatId,
3153
- namespace: input.sessionNamespace,
3154
- path: "/v1/messages",
3155
- });
3172
+ if (input.inboxExecution && submittedUser) {
3173
+ if (sessionExecution) {
3174
+ await mutateSession({ messages: [{ ...submittedUser }] });
3175
+ }
3176
+ else {
3177
+ await appendSessionValue({
3178
+ body: { message: submittedUser, runId: payload.runId },
3179
+ chatId: payload.chatId,
3180
+ namespace: input.sessionNamespace,
3181
+ path: "/v1/messages",
3182
+ });
3183
+ }
3184
+ }
3156
3185
  // Tool continuations still need the current user's durable file references
3157
3186
  // when reconstructing model messages from saved UI history.
3158
3187
  const submittedFiles = inputUser?.parts.flatMap((part) => (part.type === "file" ? [part] : [])) ??
@@ -3306,7 +3335,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
3306
3335
  if (!runtime) {
3307
3336
  throw new Error(`Custom Connector runtime is missing: ${definition.alias}`);
3308
3337
  }
3309
- if (!definition.exposeToModel)
3338
+ if (!isAgentConnectorExposed(definition, input.snapshot.codeMode))
3310
3339
  continue;
3311
3340
  if (definition.authorization === "interactive") {
3312
3341
  registerAgentWorkerTool(tools, `${definition.alias}__connect`, tool({
@@ -3615,7 +3644,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
3615
3644
  schema: z.object({ message: z.unknown() }),
3616
3645
  });
3617
3646
  const [message] = await validateUIMessages({
3618
- messages: [saved.message],
3647
+ messages: [repairLegacyToolDenials(saved.message)],
3619
3648
  });
3620
3649
  if (!message || message.role !== "assistant")
3621
3650
  throw new Error("Suspended response is not an assistant message.");
@@ -3740,14 +3769,9 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
3740
3769
  .filter((file) => file.mediaType.startsWith("image/"))
3741
3770
  .map(({ url: _url, ...file }) => file);
3742
3771
  const turnImages = new Map();
3743
- const addImage = async (file) => {
3744
- const reference = file.id ?? file.key;
3745
- if (turnImages.has(reference))
3746
- return;
3772
+ const readImage = async (file) => {
3747
3773
  if (!artifactScope)
3748
3774
  throw new Error("Images require Agent Artifacts.");
3749
- if (turnImages.size >= MAX_AGENT_ARTIFACT_IMAGES)
3750
- throw new Error(`At most ${MAX_AGENT_ARTIFACT_IMAGES} images can be viewed in one user turn.`);
3751
3775
  const response = await host.fetch(`${HOST_PATH}/tools/attachments/read`, {
3752
3776
  method: "POST",
3753
3777
  headers: { "content-type": "application/json" },
@@ -3762,11 +3786,19 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
3762
3786
  const bytes = new Uint8Array(await response.arrayBuffer());
3763
3787
  if (bytes.byteLength > MAX_AGENT_ARTIFACT_IMAGE_BYTES)
3764
3788
  throw new Error("Prepared image exceeds the model image limit.");
3765
- turnImages.set(reference, {
3789
+ return {
3766
3790
  type: "file",
3767
3791
  data: { type: "data", data: bytes },
3768
3792
  mediaType: response.headers.get("content-type") ?? file.mediaType,
3769
- });
3793
+ };
3794
+ };
3795
+ const addImage = async (file) => {
3796
+ const reference = file.id ?? file.key;
3797
+ if (turnImages.has(reference))
3798
+ return;
3799
+ if (turnImages.size >= MAX_AGENT_ARTIFACT_IMAGES)
3800
+ throw new Error(`At most ${MAX_AGENT_ARTIFACT_IMAGES} images can be viewed in one user turn.`);
3801
+ turnImages.set(reference, await readImage(file));
3770
3802
  context.metadata.turnImages ??= [];
3771
3803
  if (!context.metadata.turnImages.some((image) => (image.id ?? image.key) === reference))
3772
3804
  context.metadata.turnImages.push(file);
@@ -4040,12 +4072,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
4040
4072
  const coreConnections = new Map([[configuredModel, coreConnection]]);
4041
4073
  const coreCacheStates = new Map([[configuredModel, promptCacheState]]);
4042
4074
  const preparePrompt = async (messages) => {
4043
- messages = await resolveToolArtifacts(messages, (toolName, toolCallId) => artifactScope
4044
- ? createAgentArtifacts({
4045
- scope: artifactScope,
4046
- invoke: (operation) => invokeArtifact(operation, toolName, { toolCallId }),
4047
- })
4048
- : undefined);
4075
+ messages = await resolveToolArtifacts(messages, (id, mediaType) => readImage({ id, mediaType }));
4049
4076
  const instructionCount = payload.instructions.length;
4050
4077
  const prepared = addCacheControlToMessages([...payload.instructions, ...messages], (coreModel === configuredModel
4051
4078
  ? payload.cacheControlProvider
@@ -4293,6 +4320,33 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
4293
4320
  },
4294
4321
  };
4295
4322
  };
4323
+ if (input.snapshot.codeMode) {
4324
+ const configured = await createCodeMode({
4325
+ scriptRuntime: input.toolRuntime.actions.executeJavaScript?.scriptRuntime,
4326
+ scriptToolNames: Object.entries(input.toolRuntime.actions)
4327
+ .filter(([, action]) => action.scriptRuntime)
4328
+ .map(([name]) => name),
4329
+ readArtifact: artifactScope
4330
+ ? async (id, execution) => createAgentArtifacts({
4331
+ scope: artifactScope,
4332
+ invoke: (operation) => invokeArtifact(operation, "executeJavaScript", execution),
4333
+ }).readText({ id })
4334
+ : undefined,
4335
+ tools,
4336
+ snapshot: input.snapshot,
4337
+ approval: toolApproval,
4338
+ messages: () => context.messages,
4339
+ guardrail: engine?.guardrail,
4340
+ sdk: {
4341
+ tracing: createAgentTracing(),
4342
+ auth: initialization.runtime.auth,
4343
+ env: agentEnvironment,
4344
+ environment: initialization.runtime.environment,
4345
+ identity,
4346
+ },
4347
+ });
4348
+ tools = configured.tools;
4349
+ }
4296
4350
  let stream;
4297
4351
  if (engine) {
4298
4352
  if (!coreConnection || !coreModule)
@@ -4657,6 +4711,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
4657
4711
  async execute({ writer }) {
4658
4712
  for await (const chunk of projectAgentCoreEvents(tracked, {
4659
4713
  messageId,
4714
+ approvals: pending?.pendingStep.approvals,
4660
4715
  tools,
4661
4716
  messageMetadata: {
4662
4717
  trace: invocationTrace,
@@ -0,0 +1,38 @@
1
+ import type { AgentToolNamespace } from "@gea-ai/contract/agent-packages";
2
+ export type CodeModeCatalogEntry = {
3
+ name: string;
4
+ namespace: string;
5
+ description: string;
6
+ inputSchema: unknown;
7
+ outputSchema?: unknown;
8
+ structuredContentSchema?: unknown;
9
+ declaration: string;
10
+ mcp: boolean;
11
+ };
12
+ type Namespace = AgentToolNamespace & {
13
+ name: string;
14
+ };
15
+ /** Bounded hints, never a substitute for validation by the original tool schema. */
16
+ export declare function codeModeDeclaration(name: string, input: unknown, output: unknown, mcp: boolean): string;
17
+ /** Pi-compatible helper shapes, implemented against the GEA callable snapshot. */
18
+ export declare function createCodeModeDiscovery(catalog: CodeModeCatalogEntry[], namespaces: Namespace[]): {
19
+ allTools: {
20
+ name: string;
21
+ description: string;
22
+ }[];
23
+ searchTools(query: string, options?: {
24
+ limit?: number;
25
+ namespace?: string | null;
26
+ } | null): {
27
+ name: string;
28
+ description: string;
29
+ }[];
30
+ describeTool(name: string): string | undefined;
31
+ describeNamespace(name: string): {
32
+ description?: string | undefined;
33
+ instructions?: string | undefined;
34
+ name: string;
35
+ tools: string[];
36
+ } | undefined;
37
+ };
38
+ export {};