@gea-ai/agent-sdk 0.1.261007-alpha.1 → 0.1.261009-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.
@@ -58,6 +58,13 @@ and packaged Worker conformance fixtures before changing the pin.
58
58
 
59
59
  ## Agent Core WASM
60
60
 
61
+ The bundled Core's Anthropic output-schema sanitizer is ported from
62
+ `@ai-sdk/anthropic` **4.0.67**, `src/sanitize-json-schema.ts` (Vercel AI SDK).
63
+ Copyright 2023 Vercel, Inc.; see
64
+ [license and notice](./third-party/anthropic-schema/LICENSE).
65
+ The Rust port retains the upstream transformation using `serde_json` values;
66
+ the original schema still controls final validation.
67
+
61
68
  The optional Agent Core asset is built from this repository's Apache-2.0 Rust
62
69
  crate with Rust 1.94.1. Its Cargo dependencies are pinned in
63
70
  `crates/agent-core/Cargo.lock`; exact-version license copies and a dependency
Binary file
@@ -0,0 +1,13 @@
1
+ declare const AgentMediaReadError_base: import("better-result").TaggedErrorClass<"AgentMediaReadError", {
2
+ message: string;
3
+ operation: "download Tool output media" | "read Artifact image";
4
+ status?: number;
5
+ }>;
6
+ export declare class AgentMediaReadError extends AgentMediaReadError_base {
7
+ }
8
+ /** Translate Fetch failures at the Tool/model media boundary, without signed URLs. */
9
+ export declare function readAgentMedia(request: () => Promise<Response>, operation: "download Tool output media" | "read Artifact image", signal?: AbortSignal): Promise<{
10
+ data: ArrayBuffer;
11
+ mediaType: string | null;
12
+ }>;
13
+ export {};
@@ -0,0 +1,29 @@
1
+ import { TaggedError } from "better-result";
2
+ export class AgentMediaReadError extends TaggedError("AgentMediaReadError")() {
3
+ }
4
+ /** Translate Fetch failures at the Tool/model media boundary, without signed URLs. */
5
+ export async function readAgentMedia(request, operation, signal) {
6
+ try {
7
+ signal?.throwIfAborted();
8
+ const response = await request();
9
+ if (!response.ok)
10
+ throw new AgentMediaReadError({
11
+ operation,
12
+ status: response.status,
13
+ message: `Could not ${operation} (HTTP ${response.status}).`,
14
+ });
15
+ const data = await response.arrayBuffer();
16
+ return { data, mediaType: response.headers.get("content-type") };
17
+ }
18
+ catch (error) {
19
+ signal?.throwIfAborted();
20
+ if (AgentMediaReadError.is(error))
21
+ throw error;
22
+ // Fetch/body exceptions may contain credentials or signed URLs. These errors
23
+ // enter model feedback and traces, so retain the operation, not the raw cause.
24
+ throw new AgentMediaReadError({
25
+ operation,
26
+ message: `Could not ${operation} (network or response-body failure).`,
27
+ });
28
+ }
29
+ }
@@ -633,13 +633,18 @@ export async function handleAgentSessionInboxRequest(request, storage) {
633
633
  canonicalAgentChannelJson(input.identity))
634
634
  return Response.json({ error: "session_identity_conflict" }, { status: 409 });
635
635
  if (state.execution?.id === input.executionId) {
636
- if (path === "/v1/inbox/start") {
637
- if (state.started)
638
- return Response.json({ error: "execution_already_started" }, {
639
- status: 409,
640
- headers: { "x-gea-session-execution": "already-started" },
641
- });
642
- state.started = true;
636
+ if (input.runId && input.runId !== state.execution.turnId)
637
+ return Response.json({ error: "execution_mismatch" }, { status: 409 });
638
+ if (path === "/v1/inbox/start" && state.started)
639
+ return Response.json({ error: "execution_already_started" }, {
640
+ status: 409,
641
+ headers: { "x-gea-session-execution": "already-started" },
642
+ });
643
+ if (!state.started) {
644
+ // An inbox claim retains the previous transport until the Host
645
+ // admits this execution with its current Run authority.
646
+ state.transport = input.transport;
647
+ state.started = path === "/v1/inbox/start";
643
648
  await save(tx, state, runs);
644
649
  }
645
650
  return Response.json({ execution: state.execution });
@@ -66,6 +66,62 @@ function groupMessages(messages) {
66
66
  }
67
67
  return groups;
68
68
  }
69
+ // Provider usage is request-wide, not per message. This deliberately estimates
70
+ // content only: UTF-8 bytes / 3 plus framing, without opaque reasoning signatures
71
+ // or base64 expansion. Media gets a fixed allowance, not an exact token count.
72
+ function estimateMessageTokens(message) {
73
+ const textTokens = (text) => Math.ceil(new TextEncoder().encode(text).length / 3);
74
+ const mediaTokens = 4_096;
75
+ if (typeof message.content === "string")
76
+ return 8 + textTokens(message.content);
77
+ return (8 +
78
+ message.content.reduce((total, part) => {
79
+ switch (part.type) {
80
+ case "text":
81
+ case "reasoning":
82
+ return total + 8 + textTokens(part.text);
83
+ case "image":
84
+ case "reasoning-file":
85
+ return total + mediaTokens;
86
+ case "file":
87
+ return (total +
88
+ (typeof part.data === "object" &&
89
+ "type" in part.data &&
90
+ part.data.type === "text"
91
+ ? textTokens(part.data.text)
92
+ : mediaTokens));
93
+ case "tool-call":
94
+ return (total +
95
+ 16 +
96
+ textTokens(part.toolName) +
97
+ textTokens(JSON.stringify(part.input) ?? ""));
98
+ case "tool-result": {
99
+ const output = part.output;
100
+ let tokens;
101
+ switch (output.type) {
102
+ case "content":
103
+ tokens = output.value.reduce((count, item) => count +
104
+ (item.type === "text"
105
+ ? textTokens(item.text)
106
+ : item.type === "file" && item.data.type === "text"
107
+ ? textTokens(item.data.text)
108
+ : mediaTokens), 0);
109
+ break;
110
+ case "text":
111
+ case "error-text":
112
+ tokens = textTokens(output.value);
113
+ break;
114
+ default:
115
+ tokens = textTokens(JSON.stringify(output));
116
+ }
117
+ return total + 16 + textTokens(part.toolName) + tokens;
118
+ }
119
+ default: {
120
+ return total + 8 + textTokens(JSON.stringify(part));
121
+ }
122
+ }
123
+ }, 0));
124
+ }
69
125
  function getToolOutputText(part) {
70
126
  switch (part.output.type) {
71
127
  case "error-text":
@@ -224,7 +280,7 @@ export async function applyAgentWorkerToolOutputOffload(input) {
224
280
  return input.messages;
225
281
  const groups = groupMessages(input.messages);
226
282
  const firstProtectedIndex = input.protectRecentGroups
227
- ? Math.max(0, groups.length - input.options.preserveRecentGroups)
283
+ ? Math.max(0, groups.length - (input.options.preserveRecentGroups ?? 5))
228
284
  : groups.length;
229
285
  const messages = [];
230
286
  for (const [groupIndex, group] of groups.entries()) {
@@ -263,13 +319,135 @@ function summarySchema() {
263
319
  "## Critical Exact Details\n- Preserve exact IDs, commands, paths, API names, error messages, and config values.",
264
320
  ].join("\n\n");
265
321
  }
322
+ // Summary input is a textual projection, not a provider request. Keep resource
323
+ // references for recovery but never stringify media bytes or opaque metadata.
324
+ function summaryFileData(data) {
325
+ if (data instanceof URL) {
326
+ return data.protocol === "data:"
327
+ ? { omitted: "inline media bytes" }
328
+ : { url: data.href };
329
+ }
330
+ if (typeof data === "string" ||
331
+ data instanceof ArrayBuffer ||
332
+ ArrayBuffer.isView(data)) {
333
+ return { omitted: "inline media bytes; recover from raw chat history" };
334
+ }
335
+ if ("type" in data) {
336
+ switch (data.type) {
337
+ case "text":
338
+ return { text: data.text };
339
+ case "url": {
340
+ const url = String(data.url);
341
+ return url.startsWith("data:")
342
+ ? { omitted: "inline media bytes" }
343
+ : { url };
344
+ }
345
+ case "reference":
346
+ return { reference: data.reference };
347
+ case "data":
348
+ return { omitted: "inline media bytes; recover from raw chat history" };
349
+ }
350
+ }
351
+ return { reference: data };
352
+ }
353
+ function summaryContentPart(part) {
354
+ switch (part.type) {
355
+ case "text":
356
+ case "reasoning":
357
+ return { type: part.type, text: part.text };
358
+ case "tool-call":
359
+ return {
360
+ type: part.type,
361
+ toolName: part.toolName,
362
+ toolCallId: part.toolCallId,
363
+ input: part.input,
364
+ providerExecuted: part.providerExecuted,
365
+ };
366
+ case "tool-result": {
367
+ const output = part.output;
368
+ return {
369
+ type: part.type,
370
+ toolName: part.toolName,
371
+ toolCallId: part.toolCallId,
372
+ output: output.type === "content"
373
+ ? { type: output.type, value: output.value.map(summaryContentPart) }
374
+ : output.type === "execution-denied"
375
+ ? { type: output.type, reason: output.reason }
376
+ : { type: output.type, value: output.value },
377
+ };
378
+ }
379
+ case "file":
380
+ case "file-data":
381
+ return {
382
+ type: part.type,
383
+ mediaType: part.mediaType,
384
+ filename: part.filename,
385
+ data: summaryFileData(part.data),
386
+ };
387
+ case "image":
388
+ return {
389
+ type: part.type,
390
+ mediaType: part.mediaType,
391
+ data: summaryFileData(part.image),
392
+ };
393
+ case "image-data":
394
+ case "reasoning-file":
395
+ return {
396
+ type: part.type,
397
+ mediaType: part.mediaType,
398
+ data: summaryFileData(part.data),
399
+ };
400
+ case "file-url":
401
+ case "image-url":
402
+ return {
403
+ type: part.type,
404
+ mediaType: "mediaType" in part ? part.mediaType : undefined,
405
+ url: part.url.startsWith("data:") ? "[inline media omitted]" : part.url,
406
+ };
407
+ case "file-id":
408
+ case "image-file-id":
409
+ return { type: part.type, fileId: part.fileId };
410
+ case "file-reference":
411
+ case "image-file-reference":
412
+ return { type: part.type, reference: part.providerReference };
413
+ case "custom":
414
+ return {
415
+ type: part.type,
416
+ kind: "kind" in part ? part.kind : undefined,
417
+ omitted: "opaque provider content; recover from raw chat history",
418
+ };
419
+ case "tool-approval-request":
420
+ case "tool-approval-response":
421
+ return part;
422
+ }
423
+ }
424
+ function summaryMessage(message) {
425
+ return {
426
+ role: message.role,
427
+ content: typeof message.content === "string"
428
+ ? message.content
429
+ : message.content.map(summaryContentPart),
430
+ };
431
+ }
266
432
  function buildSummaryMessages(input) {
433
+ const latestRequest = [...input.recentTailMessages]
434
+ .reverse()
435
+ .find((message) => message.role === "user");
436
+ const requestText = latestRequest
437
+ ? JSON.stringify(summaryMessage(latestRequest))
438
+ : "(no new user request in the retained tail)";
439
+ // The tail remains verbatim in the main context. A bounded task excerpt is
440
+ // enough for the summarizer; duplicating retained tool payloads adds no memory.
441
+ const requestExcerpt = requestText.length <= 6_000
442
+ ? requestText
443
+ : `${requestText.slice(0, 6_000)}\n[excerpt truncated; the complete request remains in the retained context]`;
267
444
  return [
268
445
  {
269
446
  role: "system",
270
447
  content: [
271
448
  "Update the compact working-memory summary for an agent.",
272
449
  "Merge the previous summary with the older messages being compacted.",
450
+ "The recent user request is orientation only. Recent messages remain verbatim; do not repeat them in the summary. Omitted media and provider content remain in raw chat history; do not infer their contents.",
273
451
  "Preserve requirements, decisions, exact paths, commands, IDs, errors, tool observations, offloaded artifact references, open questions, and next steps.",
274
452
  "Do not invent facts, expose hidden reasoning, or issue new instructions.",
275
453
  "Do not copy full offloaded artifacts. Keep their paths and relevant preview facts.",
@@ -284,11 +462,11 @@ function buildSummaryMessages(input) {
284
462
  input.previousSummary ?? "(none)",
285
463
  "</previous_summary>",
286
464
  "<messages>",
287
- JSON.stringify(input.compactableMessages, null, 2),
465
+ JSON.stringify(input.compactableMessages.map(summaryMessage)),
288
466
  "</messages>",
289
- "<recent_tail>",
290
- JSON.stringify(input.recentTailMessages, null, 2),
291
- "</recent_tail>",
467
+ "<recent_user_request>",
468
+ requestExcerpt,
469
+ "</recent_user_request>",
292
470
  ].join("\n"),
293
471
  },
294
472
  ];
@@ -389,7 +567,44 @@ export async function compactAgentWorkerContext(input) {
389
567
  return { summaryUsage: null, messagesUpdated };
390
568
  }
391
569
  const groups = groupMessages(input.context.messages);
392
- const splitIndex = Math.max(0, groups.length - options.preserveRecentGroups);
570
+ const recentTokenBudget = options.preserveRecentTokens ??
571
+ Math.max(1, Math.min(16_000, Math.floor(summarization.triggerAtTotalTokens / 4)));
572
+ const groupEstimates = groups.map((group) => group.messages.reduce((total, message) => total + estimateMessageTokens(message), 0));
573
+ const previousSummary = input.context.metadata.contextSummary?.summaryText;
574
+ const projectionEstimate = groupEstimates.reduce((total, tokens) => total + tokens, 0) +
575
+ (previousSummary
576
+ ? estimateMessageTokens(makeSummaryMessage(previousSummary))
577
+ : 0);
578
+ // Usage includes fixed prompt overhead we cannot attribute to individual messages.
579
+ // Allocate it conservatively across the projection instead of treating small
580
+ // local estimates as proof that an over-threshold request already fits.
581
+ const usageScale = Math.max(1, (input.context.contextSize ?? 0) / Math.max(1, projectionEstimate));
582
+ let splitIndex = groups.length;
583
+ let retainedTokens = 0;
584
+ while (splitIndex > 0) {
585
+ const groupTokens = Math.ceil(groupEstimates[splitIndex - 1] * usageScale);
586
+ // Always keep the newest complete group, even when it exceeds the budget.
587
+ if (splitIndex < groups.length &&
588
+ (retainedTokens + groupTokens > recentTokenBudget ||
589
+ (options.preserveRecentGroups !== undefined &&
590
+ groups.length - splitIndex >= options.preserveRecentGroups)))
591
+ break;
592
+ retainedTokens += groupTokens;
593
+ splitIndex--;
594
+ }
595
+ // Fixed overhead alone is not a reason to pay for summarizing a tiny history.
596
+ // Explicit retention limits still take precedence over this default safeguard.
597
+ if (options.preserveRecentTokens === undefined &&
598
+ options.preserveRecentGroups === undefined &&
599
+ groupEstimates
600
+ .slice(0, splitIndex)
601
+ .reduce((total, tokens) => total + tokens, 0) <
602
+ Math.max(1, Math.min(1_024, Math.floor(recentTokenBudget / 4)))) {
603
+ createAgentTracing().currentSpan?.setAttributes({
604
+ "gea.context.compaction.skip_reason": "insufficient_reduction",
605
+ });
606
+ return { summaryUsage: null, messagesUpdated };
607
+ }
393
608
  const compactableMessages = groups
394
609
  .slice(0, splitIndex)
395
610
  .flatMap((group) => group.messages)
@@ -433,7 +648,18 @@ export async function compactAgentWorkerContext(input) {
433
648
  if (part.type === "content_delta" && kinds.get(part.id) === "text")
434
649
  summaryText += part.delta;
435
650
  }
436
- summaryUsage = (await result.result).usage;
651
+ const completion = await result.result;
652
+ summaryUsage = completion.usage;
653
+ if (completion.finishReason !== "stop") {
654
+ // The provider call still counts toward usage, but an incomplete
655
+ // summary must not replace the previous summary or active history.
656
+ span.setAttributes({
657
+ "gea.context.compaction.outcome": "rejected",
658
+ "gea.context.compaction.finish_reason": completion.finishReason,
659
+ "gea.context.messages.output_count": input.context.messages.length,
660
+ });
661
+ return { summaryUsage, messagesUpdated };
662
+ }
437
663
  if (!summaryText.trim())
438
664
  throw new Error("Context summary was empty.");
439
665
  const missingHeadings = REQUIRED_SUMMARY_HEADINGS.filter((heading) => !summaryText.includes(heading));
@@ -480,7 +706,11 @@ export async function compactAgentWorkerContext(input) {
480
706
  "gea.context.tokens.estimate": input.context.contextSize ?? undefined,
481
707
  "gea.context.tokens.threshold": summarization.triggerAtTotalTokens,
482
708
  "gea.context.messages.compacted_count": compactableMessages.length,
483
- "gea.context.groups.preserved": options.preserveRecentGroups,
709
+ "gea.context.groups.preserved": groups.length - splitIndex,
710
+ "gea.context.tokens.retained_estimate": retainedTokens,
711
+ "gea.context.tokens.usage_scale": usageScale,
712
+ "gea.context.tokens.retained_budget": recentTokenBudget,
713
+ "gea.context.tokens.retained_over_budget": retainedTokens > recentTokenBudget,
484
714
  },
485
715
  });
486
716
  }
@@ -1,3 +1,4 @@
1
+ import { contextSystemInputSchema } from "@gea-ai/contract/agent-worker-tools";
1
2
  import { getAgentProgrammaticToolCalling, isAgentConnectorConnectionHelperExposed, } from "@gea-ai/contract/agent-packages";
2
3
  import { AGENT_WORKER_TOOL_METADATA_HEADER, AGENT_WORKER_TOOL_METADATA_VERSION, } from "@gea-ai/contract/agent-worker-tools";
3
4
  import { isAgentConnectorExposed } from "@gea-ai/contract/agent-packages";
@@ -15,6 +16,7 @@ import { decodeAgentModelProxyValue, encodeAgentModelProxyValue, } from "@gea-ai
15
16
  import { createGeaAI, modelDescriptorSchema } from "./gea-ai.js";
16
17
  import { storeToolModelOutput, resolveToolArtifacts, } from "./tool-model-output.js";
17
18
  import { mcpToolModelOutput } from "./mcp-tool-output.js";
19
+ import { AgentMediaReadError, readAgentMedia } from "./agent-media-read.js";
18
20
  export { createGeaAI } from "./gea-ai.js";
19
21
  import { mergeLlmJson, llmRuntimeRequestOptionsSchema, } from "@gea-ai/contract/llm-catalog";
20
22
  import { executeChannelAgentRun, uploadChannelInput, } from "./agent-channel-api.js";
@@ -2231,6 +2233,21 @@ function createHostBackedAgentTools(input) {
2231
2233
  inputSchema: webSearchInputSchema,
2232
2234
  });
2233
2235
  }
2236
+ else if (definition.id === "context_system") {
2237
+ registerAgentWorkerTool(tools, "contextSystem", tool({
2238
+ description: "Access the current OAuth user's GEA Context resources. Call action=list to discover operations and their input schemas, then action=call with tool and arguments. Documents use Markdown; read before editing and retain contentHash for conflict checks. Requires OAuth; Project keys, custom principals and local calls are unsupported.",
2239
+ inputSchema: contextSystemInputSchema,
2240
+ toModelOutput: mcpToolModelOutput,
2241
+ execute: async (toolInput, options) => invokeHostTool({
2242
+ host: input.host,
2243
+ messages: options.messages,
2244
+ signal: options.abortSignal,
2245
+ toolCallId: options.toolCallId,
2246
+ toolInput,
2247
+ toolName: definition.id,
2248
+ }),
2249
+ }));
2250
+ }
2234
2251
  else if (definition.id === "agent") {
2235
2252
  // The negotiated target catalog below owns registration, including declared subagents.
2236
2253
  }
@@ -2569,7 +2586,6 @@ async function requestSessionJson(input) {
2569
2586
  method: input.method,
2570
2587
  })),
2571
2588
  });
2572
- input.onResponse?.(response);
2573
2589
  if (!response.ok)
2574
2590
  throw new Error(`AgentSession rejected ${input.path} (${response.status}).`);
2575
2591
  return input.schema.parse(await response.json());
@@ -3026,15 +3042,9 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
3026
3042
  }
3027
3043
  const mutateSession = (values) => {
3028
3044
  const queuedAt = performance.now();
3029
- const mutationSpan = trace
3030
- .getTracer("gea.agent-session")
3031
- .startSpan("agent.session.mutation", {
3032
- attributes: { "gea.agent.run.id": payload.runId },
3033
- });
3034
3045
  const result = writeTail.then(async () => {
3035
3046
  const startedAt = performance.now();
3036
3047
  const queueMs = startedAt - queuedAt;
3037
- const sequence = mutationSequence;
3038
3048
  let failed = true;
3039
3049
  try {
3040
3050
  if (writeFailure)
@@ -3046,12 +3056,6 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
3046
3056
  namespace: input.sessionNamespace,
3047
3057
  path: "/v2/mutations",
3048
3058
  schema: agentSessionExecutionMutationResultSchema,
3049
- onResponse(response) {
3050
- mutationSpan.setAttribute("http.response.status_code", response.status);
3051
- const timing = response.headers.get("server-timing");
3052
- if (timing)
3053
- mutationSpan.setAttribute("gea.session.mutation.server_timing", timing);
3054
- },
3055
3059
  });
3056
3060
  mutationSequence += 1;
3057
3061
  failed = false;
@@ -3072,22 +3076,6 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
3072
3076
  "gea.session.persistence.queue_ms": Math.round(persistenceTotals.queueMs),
3073
3077
  "gea.session.persistence.request_ms": Math.round(persistenceTotals.requestMs),
3074
3078
  });
3075
- mutationSpan.setAttributes({
3076
- "gea.session.mutation.sequence": sequence,
3077
- "gea.session.mutation.kinds": Object.keys(values),
3078
- "gea.session.mutation.event_count": values.events?.length ?? 0,
3079
- "gea.session.mutation.queue_ms": Math.round(queueMs),
3080
- "gea.session.mutation.request_ms": Math.round(requestMs),
3081
- ...(invocationTiming.lastModelFinishedAt === undefined
3082
- ? {}
3083
- : {
3084
- "gea.session.mutation.start_after_model_ms": Math.round(startedAt - invocationTiming.lastModelFinishedAt),
3085
- "gea.session.mutation.end_after_model_ms": Math.round(performance.now() - invocationTiming.lastModelFinishedAt),
3086
- }),
3087
- });
3088
- if (failed)
3089
- mutationSpan.setStatus({ code: SpanStatusCode.ERROR });
3090
- mutationSpan.end();
3091
3079
  if (failed || queueMs + requestMs >= 250)
3092
3080
  console.info("AgentSession mutation timing", {
3093
3081
  runId: payload.runId,
@@ -3802,7 +3790,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
3802
3790
  const readImage = async (file) => {
3803
3791
  if (!artifactScope)
3804
3792
  throw new Error("Images require Agent Artifacts.");
3805
- const response = await host.fetch(`${HOST_PATH}/tools/attachments/read`, {
3793
+ const image = await readAgentMedia(() => host.fetch(`${HOST_PATH}/tools/attachments/read`, {
3806
3794
  method: "POST",
3807
3795
  headers: { "content-type": "application/json" },
3808
3796
  body: JSON.stringify({
@@ -3810,16 +3798,14 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
3810
3798
  ...(file.id ? { id: file.id } : { key: file.key }),
3811
3799
  }),
3812
3800
  signal: input.request.signal,
3813
- });
3814
- if (!response.ok)
3815
- throw new Error(`Could not read Artifact image (${response.status}).`);
3816
- const bytes = new Uint8Array(await response.arrayBuffer());
3801
+ }), "read Artifact image", input.request.signal);
3802
+ const bytes = new Uint8Array(image.data);
3817
3803
  if (bytes.byteLength > MAX_AGENT_ARTIFACT_IMAGE_BYTES)
3818
3804
  throw new Error("Prepared image exceeds the model image limit.");
3819
3805
  return {
3820
3806
  type: "file",
3821
3807
  data: { type: "data", data: bytes },
3822
- mediaType: response.headers.get("content-type") ?? file.mediaType,
3808
+ mediaType: image.mediaType ?? file.mediaType,
3823
3809
  };
3824
3810
  };
3825
3811
  const addImage = async (file) => {
@@ -4115,6 +4101,12 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
4115
4101
  const initialPrompt = await startupTracing.withSpan("agent.startup.prompt", () => preparePrompt(withTurnImages(assembleAgentWorkerContext({ context, runtimeContext }), turnImages)));
4116
4102
  const modelCancellation = new AbortController();
4117
4103
  let failure;
4104
+ const executionErrorText = (error, fallback = "Agent execution failed.") => {
4105
+ const original = failure?.error ?? error;
4106
+ return original instanceof AgentMediaReadError
4107
+ ? original.message
4108
+ : fallback;
4109
+ };
4118
4110
  let pendingMainCall;
4119
4111
  let pendingMainModel = configuredModel;
4120
4112
  let lastStepNumber = 0;
@@ -4129,7 +4121,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
4129
4121
  // the execution's original failure before publishing that chunk.
4130
4122
  ownSessionOutcome({
4131
4123
  status: input.request.signal.aborted ? "cancelled" : "failed",
4132
- text: "Agent execution failed.",
4124
+ text: executionErrorText(error),
4133
4125
  });
4134
4126
  recordExecutionFailure(error);
4135
4127
  };
@@ -4267,9 +4259,9 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
4267
4259
  // both the error and final usage from clients.
4268
4260
  ownSessionOutcome({
4269
4261
  status: input.request.signal.aborted ? "cancelled" : "failed",
4270
- text: responseMessage.parts
4262
+ text: executionErrorText(failure.error, responseMessage.parts
4271
4263
  .flatMap((part) => (part.type === "text" ? [part.text] : []))
4272
- .join(""),
4264
+ .join("")),
4273
4265
  });
4274
4266
  return;
4275
4267
  }
@@ -4645,7 +4637,13 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
4645
4637
  throw failure.error;
4646
4638
  },
4647
4639
  async prepareModel(request) {
4648
- const prepared = await preparePrompt(withTurnImages(assembleAgentWorkerContext({ context, runtimeContext }), turnImages));
4640
+ const prepared = await preparePrompt(withTurnImages(assembleAgentWorkerContext({ context, runtimeContext }), turnImages)).catch((error) => {
4641
+ // Core serializes host exceptions through WASM. Preserve this safe,
4642
+ // typed media failure before that boundary loses its identity.
4643
+ if (error instanceof AgentMediaReadError)
4644
+ fail(error);
4645
+ throw error;
4646
+ });
4649
4647
  const prompt = [...prepared.instructions, ...prepared.messages];
4650
4648
  modelPrompt = prompt;
4651
4649
  const decoratedTools = addCacheControlToToolList(request.tools.map((tool) => ({
@@ -4742,6 +4740,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
4742
4740
  async execute({ writer }) {
4743
4741
  for await (const chunk of projectAgentCoreEvents(tracked, {
4744
4742
  messageId,
4743
+ onError: executionErrorText,
4745
4744
  approvals: pending?.pendingStep.approvals,
4746
4745
  tools,
4747
4746
  messageMetadata: {
@@ -4842,6 +4841,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
4842
4841
  : stepCountIs(Number.POSITIVE_INFINITY),
4843
4842
  });
4844
4843
  stream = result.toUIMessageStream({
4844
+ onError: (error) => executionErrorText(error, "An error occurred."),
4845
4845
  generateMessageId: () => payload.responseMessageId,
4846
4846
  originalMessages: payload.originalMessages,
4847
4847
  sendReasoning: true,
@@ -4948,7 +4948,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
4948
4948
  !input.request.signal.aborted) {
4949
4949
  events.push({
4950
4950
  type: "error",
4951
- errorText: "Agent execution failed.",
4951
+ errorText: executionErrorText(failure?.error),
4952
4952
  });
4953
4953
  streamedError = true;
4954
4954
  }
package/dist/index.d.ts CHANGED
@@ -10,7 +10,7 @@ export type { AgentEngine } from "./agent-core-engine.js";
10
10
  import type { AgentTracing } from "@gea-ai/contract/agent-tracing";
11
11
  export type { AgentTracing, AgentTraceSpan, } from "@gea-ai/contract/agent-tracing";
12
12
  import { type AgentContextStrategy } from "./context.js";
13
- import { remoteAgentDeclarationSchema, type AgentReference } from "@gea-ai/contract/agent-references";
13
+ import { workerAgentReferenceSchema, remoteAgentDeclarationSchema, type AgentReference } from "@gea-ai/contract/agent-references";
14
14
  export type { AgentContextStrategy, AgentContextRuntime, AgentContextPrepareStepEvent, AgentContextStepEndEvent, AgentContextEndEvent, } from "./context.js";
15
15
  import type { ComputerCommands } from "@gea-ai/contract/computer-commands";
16
16
  import type { AgentArtifacts, AgentArtifactScopeResolver } from "@gea-ai/contract/studio-artifacts";
@@ -429,7 +429,7 @@ type CustomAgentToolDefinition = {
429
429
  readonly type: "custom";
430
430
  readonly [agentToolConnectorAliases]?: Readonly<Record<string, string>>;
431
431
  };
432
- type ManagedToolId = "agent" | "chat_list" | "chat_read" | "chat_send" | "web_search";
432
+ type ManagedToolId = "agent" | "chat_list" | "chat_read" | "chat_send" | "web_search" | "context_system";
433
433
  export type DefinedManagedTool<TId extends ManagedToolId = ManagedToolId> = {
434
434
  readonly id: TId;
435
435
  readonly type: "managed";
@@ -439,7 +439,7 @@ export declare function agentTool(): DefinedManagedTool<"agent">;
439
439
  export interface WorkerAgentRegistry {
440
440
  }
441
441
  export type WorkerAgentSlug = keyof WorkerAgentRegistry extends never ? string : Extract<keyof WorkerAgentRegistry, string>;
442
- /** Explicitly makes a main Agent in the same Worker available to the owning Agent. */
442
+ /** @deprecated Use defineRemoteAgent({ slug, description }) instead. */
443
443
  export declare function defineAgentReference<const TSlug extends WorkerAgentSlug>(input: {
444
444
  slug: TSlug;
445
445
  description: string;
@@ -448,15 +448,28 @@ export declare function defineAgentReference<const TSlug extends WorkerAgentSlug
448
448
  description: string;
449
449
  slug: TSlug;
450
450
  };
451
- export type DefineRemoteAgentInput = Omit<z.input<typeof remoteAgentDeclarationSchema>, "type">;
452
- /** Invoke a fixed Agent ID. Omitted invocationAuthority uses a protected Project key; oauth requires an existing target installation. Environment defaults to the Host environment. */
453
- export declare function defineRemoteAgent(input: DefineRemoteAgentInput): {
454
- type: "remote";
455
- agentId: string;
456
- invocationAuthority?: "oauth" | "project_key" | undefined;
457
- environment?: "local" | "preview" | "production" | undefined;
451
+ export type DefineRemoteAgentInput = {
452
+ slug: WorkerAgentSlug;
458
453
  description: string;
454
+ agentId?: never;
455
+ environment?: never;
456
+ invocationAuthority?: never;
457
+ } | (Omit<z.input<typeof remoteAgentDeclarationSchema>, "type"> & {
458
+ slug?: never;
459
+ });
460
+ /** Reference a same-Worker entry by slug, or an independently admitted Agent by ID. */
461
+ export declare function defineRemoteAgent<const TSlug extends WorkerAgentSlug>(input: {
462
+ slug: TSlug;
463
+ description: string;
464
+ agentId?: never;
465
+ environment?: never;
466
+ invocationAuthority?: never;
467
+ }): Omit<z.output<typeof workerAgentReferenceSchema>, "slug"> & {
468
+ slug: TSlug;
459
469
  };
470
+ export declare function defineRemoteAgent(input: Omit<z.input<typeof remoteAgentDeclarationSchema>, "type"> & {
471
+ slug?: never;
472
+ }): z.output<typeof remoteAgentDeclarationSchema>;
460
473
  /** @deprecated Enable defineAgent({ sessionTools: true }) instead. */
461
474
  export declare function chatList(): DefinedManagedTool<"chat_list">;
462
475
  /** @deprecated Enable defineAgent({ sessionTools: true }) instead. */
@@ -464,6 +477,8 @@ export declare function chatRead(): DefinedManagedTool<"chat_read">;
464
477
  /** @deprecated Enable defineAgent({ sessionTools: true }) instead. */
465
478
  export declare function chatSend(): DefinedManagedTool<"chat_send">;
466
479
  export declare function webSearch(): DefinedManagedTool<"web_search">;
480
+ /** Access the current OAuth user's GEA Context without a separate Connection. */
481
+ export declare function contextSystem(): DefinedManagedTool<"context_system">;
467
482
  type LegacyAgentToolPreset = {
468
483
  readonly preset: "coding" | "default";
469
484
  readonly type: "preset";
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { agentToolOutputOffloadOptionsSchema, } from "@gea-ai/contract/agent-con
3
3
  import { resolveModelOptions, } from "@gea-ai/contract/model-options";
4
4
  export { aiSdk } from "./ai-sdk.js";
5
5
  import { describeAgentContext } from "./context.js";
6
- import { workerAgentReferenceSchema, remoteAgentDeclarationSchema, } from "@gea-ai/contract/agent-references";
6
+ import { workerAgentReferenceSchema, remoteAgentDeclarationSchema, remoteAgentInputSchema, } from "@gea-ai/contract/agent-references";
7
7
  import { agentConnectorAuthSchema, agentConnectorDefinitionSchema, agentConnectorGrantSchema, agentConnectorRuntimeSchema, agentEnvironmentVariableSchema, getAgentConnectorPrincipalType, agentPackageSnapshotSchema, agentSlugSchema, agentSkillSchema, agentToolSchema, GEA_AGENT_PACKAGE_SCHEMA_VERSION, } from "@gea-ai/contract/agent-packages";
8
8
  import { agentChannelKeySchema, agentChannelPathSchema, agentCustomChannelDefinitionSchema, agentHttpChannelDefinitionSchema, canonicalAgentChannelJson, } from "@gea-ai/contract/agent-channels";
9
9
  import { z } from "zod";
@@ -339,7 +339,7 @@ const agentToolConnectorAliases = Symbol("agentToolConnectorAliases");
339
339
  export function agentTool() {
340
340
  return { id: "agent", type: "managed" };
341
341
  }
342
- /** Explicitly makes a main Agent in the same Worker available to the owning Agent. */
342
+ /** @deprecated Use defineRemoteAgent({ slug, description }) instead. */
343
343
  export function defineAgentReference(input) {
344
344
  const reference = workerAgentReferenceSchema.parse({
345
345
  ...input,
@@ -347,9 +347,11 @@ export function defineAgentReference(input) {
347
347
  });
348
348
  return { ...reference, slug: input.slug };
349
349
  }
350
- /** Invoke a fixed Agent ID. Omitted invocationAuthority uses a protected Project key; oauth requires an existing target installation. Environment defaults to the Host environment. */
351
350
  export function defineRemoteAgent(input) {
352
- return remoteAgentDeclarationSchema.parse({ ...input, type: "remote" });
351
+ const reference = remoteAgentInputSchema.parse(input);
352
+ return "slug" in reference
353
+ ? workerAgentReferenceSchema.parse({ ...reference, type: "worker" })
354
+ : remoteAgentDeclarationSchema.parse({ ...reference, type: "remote" });
353
355
  }
354
356
  /** @deprecated Enable defineAgent({ sessionTools: true }) instead. */
355
357
  export function chatList() {
@@ -366,6 +368,10 @@ export function chatSend() {
366
368
  export function webSearch() {
367
369
  return { id: "web_search", type: "managed" };
368
370
  }
371
+ /** Access the current OAuth user's GEA Context without a separate Connection. */
372
+ export function contextSystem() {
373
+ return { id: "context_system", type: "managed" };
374
+ }
369
375
  export function defineToolSet(tools) {
370
376
  return { tools, type: "tool-set" };
371
377
  }
@@ -1,5 +1,6 @@
1
1
  import { convertInlineFileDataToUint8Array } from "@ai-sdk/provider-utils";
2
2
  import { z } from "zod";
3
+ import { readAgentMedia } from "./agent-media-read.js";
3
4
  const uploadResultSchema = z.object({ id: z.uuid() });
4
5
  /** Persist only immutable references in the model context, never signed URLs or bytes. */
5
6
  export async function storeToolModelOutput(output, artifacts, signal) {
@@ -29,10 +30,8 @@ export async function storeToolModelOutput(output, artifacts, signal) {
29
30
  });
30
31
  let body;
31
32
  if (part.data.type === "url") {
32
- const response = await fetch(part.data.url, { signal });
33
- if (!response.ok)
34
- throw new Error(`Tool file download failed (${response.status}).`);
35
- body = await response.arrayBuffer();
33
+ const url = part.data.url;
34
+ body = (await readAgentMedia(() => fetch(url, { signal }), "download Tool output media", signal)).data;
36
35
  }
37
36
  else {
38
37
  const bytes = convertInlineFileDataToUint8Array(part.data);
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const agentSdkVersion = "0.1.261007-alpha.1";
1
+ export declare const agentSdkVersion = "0.1.261009-alpha.0";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by scripts/build-sdk-version.mjs from package.json.
2
- export const agentSdkVersion = "0.1.261007-alpha.1";
2
+ export const agentSdkVersion = "0.1.261009-alpha.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gea-ai/agent-sdk",
3
- "version": "0.1.261007-alpha.1",
3
+ "version": "0.1.261009-alpha.0",
4
4
  "private": false,
5
5
  "homepage": "https://musegea.com/developers",
6
6
  "license": "Apache-2.0",
@@ -119,7 +119,7 @@
119
119
  "@ai-sdk/otel": "1.0.119",
120
120
  "@ai-sdk/provider": "4.0.19",
121
121
  "@ai-sdk/provider-utils": "5.0.50",
122
- "@gea-ai/contract": "0.1.261007-alpha.1",
122
+ "@gea-ai/contract": "0.1.261009-alpha.0",
123
123
  "@opentelemetry/api": "1.9.1",
124
124
  "@opentelemetry/context-async-hooks": "2.10.0",
125
125
  "@opentelemetry/core": "2.10.0",
@@ -0,0 +1,13 @@
1
+ Copyright 2023 Vercel, Inc.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.