@gea-ai/agent-sdk 0.1.261019-alpha.1 → 0.1.261021-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.
@@ -23,6 +23,7 @@ const sessionSchema = z
23
23
  })
24
24
  .nullable();
25
25
  const receiptSchema = z.object({
26
+ queued: z.boolean().default(false),
26
27
  runId: z.uuid(),
27
28
  inputId: z.string().min(1),
28
29
  conversationId: z.string(),
@@ -231,6 +231,7 @@ export async function handleCustomChannelAddressRequest(request, storage, enviro
231
231
  });
232
232
  return json({
233
233
  conversationId: config.address.providerLocalKey,
234
+ queued: receipt.queued,
234
235
  duplicate: receipt.duplicate,
235
236
  executionTarget: receipt.executionTarget,
236
237
  operationId: operation.input.operationId,
@@ -244,7 +244,9 @@ export async function createAgentComputerSession(input) {
244
244
  }
245
245
  : { processRequest: undefined }),
246
246
  exec: async (id, value, opts) => {
247
- if (protocol) {
247
+ // A cancelled HTTP exec response cannot confirm process termination.
248
+ // Process-capable compatibility providers use the same owned kill/close path.
249
+ if (computer.processRequest) {
248
250
  const signal = AbortSignal.any([
249
251
  opts?.signal ?? input.signal,
250
252
  lifetime.signal,
@@ -253,6 +255,7 @@ export async function createAgentComputerSession(input) {
253
255
  const result = z.object({ processId: z.string() }).parse(await computer.processRequest(id, "process-start", {
254
256
  ...value,
255
257
  cwd: value.cwd ?? opened.roots.workspace,
258
+ stdin: false,
256
259
  timeoutMs: 120_000,
257
260
  }, { ...opts, signal }));
258
261
  return createComputerProcess({
@@ -29,34 +29,17 @@ export class SessionProtocolError extends Error {
29
29
  this.status = status;
30
30
  }
31
31
  }
32
- /**
33
- * Inbox continuation and failure takeover share one execution budget.
34
- * A stored user-input wait (ask_user / approval) keeps its attempt.
32
+ /** Waiting continues the same work; only an expired running takeover spends
33
+ * another failure attempt. Every admission still receives a fresh attemptId.
34
+ * Keep old persisted counts conservatively: they may include genuine failures.
35
35
  */
36
- function admitExecutionAttempt(active, preserveAttempt = false) {
37
- if (preserveAttempt) {
38
- if (!active)
39
- throw new SessionProtocolError("takeover_mismatch");
36
+ function admitExecutionAttempt(active) {
37
+ if (active?.status === "waiting")
40
38
  return active.attempt;
41
- }
42
39
  if (active && active.attempt >= MAX_AGENT_SESSION_EXECUTION_ATTEMPTS)
43
40
  throw new SessionProtocolError("attempts_exhausted");
44
41
  return (active?.attempt ?? 0) + 1;
45
42
  }
46
- async function resumesUserInput(tx, active) {
47
- if (active?.status !== "waiting")
48
- return false;
49
- const state = await getAgentSessionRunState(tx, active.runId);
50
- if (state?.status === "waiting" &&
51
- "inputMessageId" in state &&
52
- Boolean(state.inputMessageId))
53
- return true;
54
- // App Server enter clears inputWait before reserve admits the attempt.
55
- // The continuation was already validated against that wait.
56
- return (state?.status === "running" &&
57
- "userInputResumeMessageId" in state &&
58
- Boolean(state.userInputResumeMessageId));
59
- }
60
43
  function json(value, status = 200) {
61
44
  return Response.json(value, {
62
45
  status,
@@ -314,7 +297,7 @@ export async function startAgentSessionInvocation(tx, input) {
314
297
  activeStreamId: null,
315
298
  cancelRequestedAt: active?.cancelRequestedAt ?? null,
316
299
  ...selection,
317
- attempt: admitExecutionAttempt(active, await resumesUserInput(tx, active)),
300
+ attempt: admitExecutionAttempt(active),
318
301
  attemptId: crypto.randomUUID(),
319
302
  leaseExpiresAt: new Date(Date.now() + AGENT_SESSION_EXECUTION_LEASE_MS).toISOString(),
320
303
  leaseOwner: "host",
@@ -743,7 +726,7 @@ export async function handleAgentSessionExecutionRequest(request, storage) {
743
726
  activeStreamId: null,
744
727
  cancelRequestedAt: active?.cancelRequestedAt ?? null,
745
728
  ...selection,
746
- attempt: admitExecutionAttempt(active, await resumesUserInput(tx, active)),
729
+ attempt: admitExecutionAttempt(active),
747
730
  attemptId: crypto.randomUUID(),
748
731
  leaseExpiresAt: new Date(Date.now() + AGENT_SESSION_EXECUTION_LEASE_MS).toISOString(),
749
732
  leaseOwner: "host",
@@ -1185,6 +1185,7 @@ export async function handleAgentSessionInboxRequest(request, storage) {
1185
1185
  return Response.json({ error: "input_not_found" }, { status: 404 });
1186
1186
  const accepted = agentSessionEnqueueSchema.parse(stored);
1187
1187
  return Response.json(agentSessionEnqueueReceiptSchema.parse({
1188
+ ...(await tx.get(`inbox:admission:${id}`)),
1188
1189
  id,
1189
1190
  runId: accepted.runId,
1190
1191
  executionTarget: accepted.executionTarget,
@@ -1212,6 +1213,7 @@ export async function handleAgentSessionInboxRequest(request, storage) {
1212
1213
  canonicalAgentChannelJson(input))
1213
1214
  return Response.json({ error: "message_identity_conflict" }, { status: 409 });
1214
1215
  return Response.json(agentSessionEnqueueReceiptSchema.parse({
1216
+ ...(await tx.get(`inbox:admission:${accepted.message.id}`)),
1215
1217
  id: accepted.message.id,
1216
1218
  runId: accepted.runId,
1217
1219
  executionTarget: accepted.executionTarget,
@@ -1227,8 +1229,37 @@ export async function handleAgentSessionInboxRequest(request, storage) {
1227
1229
  (await tx.get(`inbox:message:${input.message.id}`)) !== undefined)
1228
1230
  return Response.json({ error: "message_identity_conflict" }, { status: 409 });
1229
1231
  if (state.messages.length >= 128 ||
1230
- new TextEncoder().encode(JSON.stringify(state) + JSON.stringify(input)).byteLength > 2_000_000)
1231
- return Response.json({ error: "inbox_full" }, { status: 429 });
1232
+ new TextEncoder().encode(JSON.stringify(state) + JSON.stringify(input)).byteLength > 2_000_000) {
1233
+ if (!input.channel)
1234
+ return Response.json({ error: "inbox_full" }, { status: 429 });
1235
+ // Freeze Channel rejection before any ACK/feedback. Replays must never
1236
+ // become accepted after capacity frees, even if this response is lost.
1237
+ // A first input can exceed the byte bound before an inbox exists.
1238
+ await writeState(tx, state);
1239
+ await putSessionPayload(tx, key, input);
1240
+ await tx.put(`inbox:admission:${input.message.id}`, {
1241
+ queued: false,
1242
+ rejected: true,
1243
+ });
1244
+ return Response.json(agentSessionEnqueueReceiptSchema.parse({
1245
+ id: input.message.id,
1246
+ runId: input.runId,
1247
+ executionTarget: input.executionTarget,
1248
+ duplicate: false,
1249
+ queued: false,
1250
+ rejected: true,
1251
+ }));
1252
+ }
1253
+ const requiresQueue = Boolean(state.messages.length ||
1254
+ state.execution ||
1255
+ state.wait ||
1256
+ state.inputWait ||
1257
+ state.draining ||
1258
+ state.terminal ||
1259
+ state.queuePaused);
1260
+ await tx.put(`inbox:admission:${input.message.id}`, {
1261
+ queued: requiresQueue,
1262
+ });
1232
1263
  const message = {
1233
1264
  ...input.message,
1234
1265
  runId: input.runId,
@@ -1240,6 +1271,7 @@ export async function handleAgentSessionInboxRequest(request, storage) {
1240
1271
  await tx.put(`inbox:enqueue-run:${input.runId}`, message.id);
1241
1272
  await save(tx, state, runs);
1242
1273
  return Response.json(agentSessionEnqueueReceiptSchema.parse({
1274
+ queued: requiresQueue,
1243
1275
  id: message.id,
1244
1276
  runId: input.runId,
1245
1277
  executionTarget: input.executionTarget,
@@ -1315,6 +1347,31 @@ export async function handleAgentSessionInboxRequest(request, storage) {
1315
1347
  executionTarget: agentSessionRunTargetSchema.parse(target),
1316
1348
  });
1317
1349
  }
1350
+ case "settled": {
1351
+ z.strictObject({}).parse(body);
1352
+ if (!state.appServer)
1353
+ return Response.json({ error: "execution_unconfirmed" }, { status: 409 });
1354
+ const children = await readChildRunIndex(tx);
1355
+ // Inspect the complete owner state, not the filtered public snapshot.
1356
+ const unfinished = state.execution ||
1357
+ state.wait ||
1358
+ state.inputWait ||
1359
+ state.messages.length ||
1360
+ state.pendingStarts.length ||
1361
+ state.terminal ||
1362
+ state.draining ||
1363
+ state.terminalQueue.length ||
1364
+ state.inputNotification ||
1365
+ runs.some((run) => !run.outcome) ||
1366
+ children?.reservations.length ||
1367
+ (await activeComputerOperation(tx)) ||
1368
+ (await tx.get("execution-v2:active-turn")) ||
1369
+ (await tx.get("active-turn")) ||
1370
+ (await tx.list({ prefix: "inbox:terminal:", limit: 1 })).size;
1371
+ return unfinished
1372
+ ? Response.json({ error: "execution_unfinished" }, { status: 409 })
1373
+ : Response.json({ settled: true });
1374
+ }
1318
1375
  case "snapshot": {
1319
1376
  if (!state.appServer)
1320
1377
  return Response.json({ error: "legacy_session_read_only" }, { status: 409 });
@@ -1,3 +1,4 @@
1
+ import { recordModelRequest, recordModelSkills } from "./model-diagnostics.js";
1
2
  import { agentModelTerminationSchema, } from "@gea-ai/contract/agent-usage";
2
3
  export { uploadChannelInput } from "./agent-channel-api.js";
3
4
  import { createChannelSessionNamespace } from "./agent-channel-session-transport.js";
@@ -3406,6 +3407,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
3406
3407
  }
3407
3408
  input = { ...input, bindings: { ...scopedBindings, computer } };
3408
3409
  }
3410
+ await recordModelSkills(input.skillMount?.files ?? []);
3409
3411
  const skillTool = createAgentSkillTool({
3410
3412
  files: input.skillMount?.files ?? [],
3411
3413
  mountedRoot: mountedSkillRoot,
@@ -4291,8 +4293,14 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
4291
4293
  let failure;
4292
4294
  const executionErrorText = (error, fallback = "Agent execution failed.") => {
4293
4295
  const original = failure?.error ?? error;
4294
- return original instanceof AgentMediaReadError
4295
- ? original.message
4296
+ if (original instanceof AgentMediaReadError)
4297
+ return original.message;
4298
+ // Copy only the opaque Host correlation ID across the masked UI boundary.
4299
+ const diagnosticId = original instanceof Error
4300
+ ? /Diagnostic ID: ([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i.exec(original.message)?.[1]
4301
+ : undefined;
4302
+ return diagnosticId
4303
+ ? `${fallback} Diagnostic ID: ${diagnosticId}`
4296
4304
  : fallback;
4297
4305
  };
4298
4306
  let executionDiagnostics;
@@ -4778,6 +4786,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
4778
4786
  onError: engine.onError,
4779
4787
  async fetch(request, init) {
4780
4788
  const startedAt = performance.now();
4789
+ await recordModelRequest(init?.body, coreConnection.provider.format, modelSpan);
4781
4790
  const response = await coreConnection.fetch(request, init);
4782
4791
  modelSpan?.setAttributes({
4783
4792
  "gea.model.protocol": coreConnection.provider.format,
@@ -1,3 +1,4 @@
1
+ import { recordModelRequest } from "./model-diagnostics.js";
1
2
  import { moveOpenAICompatibleCacheControlToMessageContent } from "./openai-compatible-cache-control.js";
2
3
  export { moveOpenAICompatibleCacheControlToMessageContent } from "./openai-compatible-cache-control.js";
3
4
  import { createAnthropic } from "@ai-sdk/anthropic";
@@ -125,6 +126,7 @@ export function createCatalogLanguageModel(config) {
125
126
  headers.delete("authorization");
126
127
  headers.delete("x-api-key");
127
128
  headers.set(config.auth === "bearer" ? "authorization" : "x-api-key", config.auth === "bearer" ? `Bearer ${config.apiKey}` : config.apiKey);
129
+ await recordModelRequest(body, config.format);
128
130
  return config.fetch(input, {
129
131
  ...init,
130
132
  headers,
@@ -21,6 +21,7 @@ declare const ChannelInputError_base: import("better-result").TaggedErrorClass<"
21
21
  export declare class ChannelInputError extends ChannelInputError_base {
22
22
  }
23
23
  export type ChannelSendReceipt = {
24
+ queued: boolean;
24
25
  runId: string;
25
26
  inputId: string;
26
27
  conversationId: string;
@@ -1,3 +1,4 @@
1
+ import { recordModelRequest } from "./model-diagnostics.js";
1
2
  import { SpanStatusCode, trace } from "@opentelemetry/api";
2
3
  import { AgentCoreError, createAgentCore, runAgentCore, } from "./experimental/agent-core.js";
3
4
  import { fromModelMessages, toModelMessages, } from "./experimental/agent-core-messages.js";
@@ -40,7 +41,10 @@ export async function runJudgeCore(input) {
40
41
  });
41
42
  const turn = runAgentCore(core, { type: "start" }, {
42
43
  ...tools,
43
- fetch: connection.fetch,
44
+ async fetch(request, init) {
45
+ await recordModelRequest(init?.body, connection.provider.format, modelSpan);
46
+ return connection.fetch(request, init);
47
+ },
44
48
  async prepareStep(value) {
45
49
  step = value.step;
46
50
  input.onStep(step);
@@ -0,0 +1,14 @@
1
+ import { type Span } from "@opentelemetry/api";
2
+ export declare const modelDiagnosticContext: symbol;
3
+ export type ModelDiagnosticContext = {
4
+ skillsSha256?: string;
5
+ };
6
+ /** Sort object keys recursively; preserve array order (including anyOf/ref lists). */
7
+ export declare function canonicalHash(value: unknown): Promise<string>;
8
+ export declare function recordModelSkills(files: ReadonlyArray<{
9
+ path: string;
10
+ dataBase64: string;
11
+ }>): Promise<void>;
12
+ /** Called after codec/default merging, immediately before transport. Never retain
13
+ * headers, endpoints, user messages, schema text, or arbitrary provider options. */
14
+ export declare function recordModelRequest(body: unknown, format: string, span?: Span | undefined): Promise<void>;
@@ -0,0 +1,117 @@
1
+ import { context, createContextKey, trace, } from "@opentelemetry/api";
2
+ import { z } from "zod";
3
+ export const modelDiagnosticContext = createContextKey("gea model diagnostics");
4
+ /** Sort object keys recursively; preserve array order (including anyOf/ref lists). */
5
+ export async function canonicalHash(value) {
6
+ const serialized = JSON.stringify(value, (_key, entry) => {
7
+ if (entry !== null && typeof entry === "object" && !Array.isArray(entry)) {
8
+ return Object.fromEntries(Object.entries(entry).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)));
9
+ }
10
+ return entry;
11
+ });
12
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(serialized));
13
+ return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
14
+ }
15
+ export async function recordModelSkills(files) {
16
+ const scope = context.active().getValue(modelDiagnosticContext);
17
+ if (scope)
18
+ scope.skillsSha256 = await canonicalHash([...files].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
19
+ }
20
+ const wireTool = z.object({
21
+ type: z.string().optional(),
22
+ name: z.string().optional(),
23
+ parameters: z.json().optional(),
24
+ input_schema: z.json().optional(),
25
+ strict: z.boolean().nullable().optional(),
26
+ function: z
27
+ .object({
28
+ name: z.string(),
29
+ parameters: z.json().optional(),
30
+ strict: z.boolean().nullable().optional(),
31
+ })
32
+ .optional(),
33
+ });
34
+ const wireRequest = z.object({
35
+ model: z.string().optional(),
36
+ tools: z.array(wireTool).optional(),
37
+ tool_choice: z
38
+ .union([
39
+ z.enum(["auto", "none", "required"]),
40
+ z.object({
41
+ type: z.string().max(64),
42
+ name: z.string().max(256).optional(),
43
+ function: z.object({ name: z.string().max(256) }).optional(),
44
+ disable_parallel_tool_use: z.boolean().optional(),
45
+ }),
46
+ ])
47
+ .optional(),
48
+ system: z.json().optional(),
49
+ instructions: z.json().optional(),
50
+ messages: z
51
+ .array(z.object({ role: z.string(), content: z.json().optional() }))
52
+ .optional(),
53
+ input: z
54
+ .union([
55
+ z.string(),
56
+ z.array(z
57
+ .object({ role: z.string().optional(), content: z.json().optional() })
58
+ .passthrough()),
59
+ ])
60
+ .optional(),
61
+ temperature: z.number().nullable().optional(),
62
+ top_p: z.number().nullable().optional(),
63
+ max_tokens: z.number().optional(),
64
+ max_completion_tokens: z.number().optional(),
65
+ max_output_tokens: z.number().optional(),
66
+ parallel_tool_calls: z.boolean().optional(),
67
+ stream: z.boolean().optional(),
68
+ reasoning_effort: z.string().optional(),
69
+ reasoning: z.object({ effort: z.string().optional() }).optional(),
70
+ thinking: z
71
+ .object({
72
+ type: z.string().optional(),
73
+ budget_tokens: z.number().optional(),
74
+ })
75
+ .optional(),
76
+ });
77
+ /** Called after codec/default merging, immediately before transport. Never retain
78
+ * headers, endpoints, user messages, schema text, or arbitrary provider options. */
79
+ export async function recordModelRequest(body, format, span = trace.getActiveSpan()) {
80
+ if (!span?.isRecording())
81
+ return;
82
+ const parsed = wireRequest.safeParse(typeof body === "string" ? JSON.parse(body) : body);
83
+ if (!parsed.success) {
84
+ span.setAttribute("gea.model.diagnostics.status", "unsupported-request-shape");
85
+ return;
86
+ }
87
+ const { tools, system, instructions, messages, input, ...parameters } = parsed.data;
88
+ const definitions = await Promise.all((tools ?? []).slice(0, 128).map(async (tool) => {
89
+ const definition = tool.function ?? tool;
90
+ return {
91
+ name: definition.name?.slice(0, 256),
92
+ type: tool.type ?? "function",
93
+ schemaSha256: await canonicalHash(definition.parameters ?? tool.input_schema ?? null),
94
+ strict: definition.strict ?? null,
95
+ };
96
+ }));
97
+ const systemMessages = (messages ?? (Array.isArray(input) ? input : []))
98
+ .filter((message) => message.role === "system" || message.role === "developer")
99
+ .map((message) => ({ role: message.role, content: message.content }));
100
+ const scope = context.active().getValue(modelDiagnosticContext);
101
+ span.setAttributes({
102
+ "gea.model.diagnostics.version": 1,
103
+ "gea.model.request.format": format,
104
+ "gea.model.request.parameters": JSON.stringify(parameters),
105
+ "gea.model.tools": JSON.stringify(definitions),
106
+ "gea.model.tools.count": tools?.length ?? 0,
107
+ "gea.model.tools.sha256": await canonicalHash(tools ?? []),
108
+ "gea.model.system.sha256": await canonicalHash({
109
+ system,
110
+ instructions,
111
+ messages: systemMessages,
112
+ }),
113
+ ...(scope?.skillsSha256
114
+ ? { "gea.model.skills.sha256": scope.skillsSha256 }
115
+ : {}),
116
+ });
117
+ }
package/dist/tracing.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { modelDiagnosticContext, } from "./model-diagnostics.js";
1
2
  import { agentSdkVersion } from "./version.js";
2
3
  import { Result } from "better-result";
3
4
  import { captureTraceContext } from "./trace-context.js";
@@ -166,7 +167,9 @@ export async function traceWorkerRequest(request, name, attributes, execute) {
166
167
  exports: new Set(),
167
168
  errors: [],
168
169
  };
169
- const parent = extractTraceContext(request.headers).setValue(requestTraceScope, scope);
170
+ const parent = extractTraceContext(request.headers)
171
+ .setValue(requestTraceScope, scope)
172
+ .setValue(modelDiagnosticContext, {});
170
173
  const span = currentProvider
171
174
  .getTracer("gea.agent-sdk", agentSdkVersion)
172
175
  .startSpan(name, {
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const agentSdkVersion = "0.1.261019-alpha.1";
1
+ export declare const agentSdkVersion = "0.1.261021-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.261019-alpha.1";
2
+ export const agentSdkVersion = "0.1.261021-alpha.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gea-ai/agent-sdk",
3
- "version": "0.1.261019-alpha.1",
3
+ "version": "0.1.261021-alpha.0",
4
4
  "private": false,
5
5
  "homepage": "https://musegea.com/developers",
6
6
  "license": "Apache-2.0",
@@ -123,7 +123,7 @@
123
123
  "@ai-sdk/otel": "1.0.119",
124
124
  "@ai-sdk/provider": "4.0.19",
125
125
  "@ai-sdk/provider-utils": "5.0.50",
126
- "@gea-ai/contract": "0.1.261019-alpha.1",
126
+ "@gea-ai/contract": "0.1.261021-alpha.0",
127
127
  "@opentelemetry/api": "1.9.1",
128
128
  "@opentelemetry/context-async-hooks": "2.10.0",
129
129
  "@opentelemetry/core": "2.10.0",