@gea-ai/agent-sdk 0.1.260922-alpha.2 → 0.1.260923-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.
- package/dist/agent-session-execution.js +57 -6
- package/dist/agent-session-inbox.d.ts +3 -0
- package/dist/agent-session-inbox.js +87 -76
- package/dist/agent-session-transport.d.ts +4 -0
- package/dist/agent-session-transport.js +9 -1
- package/dist/agent-stream-coalescing.d.ts +6 -0
- package/dist/agent-stream-coalescing.js +83 -0
- package/dist/agent-worker.js +156 -145
- package/dist/context-runtime.js +38 -6
- package/dist/experimental/agent-core-ui.js +7 -14
- package/package.json +2 -2
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { digestAgentSessionMessage } from "./agent-session-transport.js";
|
|
1
2
|
import { getSessionPayload, putSessionPayload } from "./agent-session-payload.js";
|
|
2
3
|
import { agentModelCallsSchema, } from "@gea-ai/contract/agent-usage";
|
|
3
|
-
import { AGENT_SESSION_COMPUTER_LEASE_MS, MAX_AGENT_SESSION_TRANSFER_BYTES, agentSessionTransferChunkSchema, agentSessionTransferReferenceSchema, agentSessionTransferIdentitySchema, agentSessionComputerSchema, agentSessionComputerReferenceSchema, AGENT_SESSION_EXECUTION_LEASE_MS, AGENT_SESSION_EXECUTION_PROTOCOL_VERSION, MAX_AGENT_SESSION_RESPONSE_BYTES, MAX_AGENT_SESSION_EXECUTION_ATTEMPTS, MAX_AGENT_SESSION_EXECUTION_BODY_BYTES, MAX_AGENT_SESSION_PAGE_SIZE, agentSessionAttemptIdentitySchema, agentSessionExecutionRenewSchema, agentSessionExecutionCompleteSchema, agentSessionExecutionPauseSchema, agentSessionExecutionControlSchema, agentSessionExecutionMutationSchema, agentSessionExecutionStartSchema, agentSessionExecutionPrepareSchema, } from "@gea-ai/contract/agent-session";
|
|
4
|
+
import { AGENT_SESSION_COMPUTER_LEASE_MS, MAX_AGENT_SESSION_TRANSFER_BYTES, agentSessionTransferChunkSchema, agentSessionTransferReferenceSchema, agentSessionTransferIdentitySchema, agentSessionComputerSchema, agentSessionComputerReferenceSchema, AGENT_SESSION_EXECUTION_LEASE_MS, AGENT_SESSION_EXECUTION_PROTOCOL_VERSION, MAX_AGENT_SESSION_RESPONSE_BYTES, MAX_AGENT_SESSION_EXECUTION_ATTEMPTS, MAX_AGENT_SESSION_EXECUTION_BODY_BYTES, MAX_AGENT_SESSION_PAGE_SIZE, agentSessionAttemptIdentitySchema, agentSessionExecutionRenewSchema, agentSessionExecutionReleaseSchema, agentSessionExecutionCompleteSchema, agentSessionExecutionCompleteStoredSchema, agentSessionExecutionPauseSchema, agentSessionExecutionControlSchema, agentSessionExecutionMutationSchema, agentSessionExecutionStartSchema, agentSessionExecutionPrepareSchema, } from "@gea-ai/contract/agent-session";
|
|
4
5
|
import { z } from "zod";
|
|
5
6
|
import { activeComputerOperation } from "./agent-session-computer.js";
|
|
6
|
-
import { isAgentSessionInboxBusy, scheduleAgentSessionInbox, getAgentSessionRunState, handleAgentSessionInboxRequest, hasAgentSessionInbox, skipAgentSessionInputWait, } from "./agent-session-inbox.js";
|
|
7
|
+
import { isAgentSessionInboxBusy, scheduleAgentSessionInbox, getAgentSessionRunState, handleAgentSessionInboxRequest, finishAgentSessionInbox, hasAgentSessionInbox, skipAgentSessionInputWait, } from "./agent-session-inbox.js";
|
|
7
8
|
// Managed execution owns its own durable state; PostgreSQL copies the live stream.
|
|
8
9
|
const prefix = "execution-v2:";
|
|
9
10
|
const key = (name) => `${prefix}${name}`;
|
|
@@ -424,6 +425,28 @@ export async function handleAgentSessionExecutionRequest(request, storage) {
|
|
|
424
425
|
});
|
|
425
426
|
return json(preparing ? { ...result, capabilities } : result);
|
|
426
427
|
}
|
|
428
|
+
if (request.method === "POST" && url.pathname === "/v2/turns/release") {
|
|
429
|
+
const input = agentSessionExecutionReleaseSchema.parse(await readBody(request, storage));
|
|
430
|
+
const startedAt = performance.now();
|
|
431
|
+
let handlerMs = 0;
|
|
432
|
+
const response = await storage.transaction(async (tx) => {
|
|
433
|
+
const handlerStartedAt = performance.now();
|
|
434
|
+
const active = await requireAttempt(tx, input);
|
|
435
|
+
const response = await finishAgentSessionInbox(tx, input.inbox, input.runId);
|
|
436
|
+
if (!response.ok)
|
|
437
|
+
return response;
|
|
438
|
+
await tx.put(key("active-turn"), {
|
|
439
|
+
...active,
|
|
440
|
+
leaseOwner: "host",
|
|
441
|
+
leaseExpiresAt: new Date(Date.now() + AGENT_SESSION_EXECUTION_LEASE_MS).toISOString(),
|
|
442
|
+
});
|
|
443
|
+
await advanceRevision(tx);
|
|
444
|
+
handlerMs = performance.now() - handlerStartedAt;
|
|
445
|
+
return response;
|
|
446
|
+
});
|
|
447
|
+
response.headers.set("server-timing", `session-handler;dur=${handlerMs.toFixed(1)}, session-transaction;dur=${(performance.now() - startedAt).toFixed(1)}`);
|
|
448
|
+
return response;
|
|
449
|
+
}
|
|
427
450
|
if (request.method === "POST" && url.pathname === "/v2/turns/renew") {
|
|
428
451
|
const input = agentSessionExecutionRenewSchema.parse(await readBody(request, storage));
|
|
429
452
|
return json(await storage.transaction(async (tx) => {
|
|
@@ -506,12 +529,39 @@ export async function handleAgentSessionExecutionRequest(request, storage) {
|
|
|
506
529
|
return { duplicate: false, turn, revision };
|
|
507
530
|
}));
|
|
508
531
|
}
|
|
509
|
-
if (request.method === "POST" &&
|
|
510
|
-
|
|
511
|
-
|
|
532
|
+
if (request.method === "POST" &&
|
|
533
|
+
(url.pathname === "/v2/turns/complete" ||
|
|
534
|
+
url.pathname === "/v2/turns/complete-stored")) {
|
|
535
|
+
const body = await readBody(request, storage);
|
|
536
|
+
const stored = url.pathname === "/v2/turns/complete-stored"
|
|
537
|
+
? agentSessionExecutionCompleteStoredSchema.parse(body)
|
|
538
|
+
: undefined;
|
|
539
|
+
const { messageReferences: _references, ...storedValues } = stored ?? {};
|
|
540
|
+
const input = agentSessionExecutionCompleteSchema.parse(stored ? storedValues : body);
|
|
541
|
+
const storedRequestDigest = stored ? await digest(stored) : undefined;
|
|
542
|
+
const standardDigest = stored ? undefined : await digest(input);
|
|
512
543
|
return json(await storage.transaction(async (tx) => {
|
|
513
544
|
const receiptKey = key(`outcome:${encodeURIComponent(input.runId)}`);
|
|
514
545
|
const existing = await tx.get(receiptKey);
|
|
546
|
+
if (stored && existing?.storedRequestDigest) {
|
|
547
|
+
if (existing.storedRequestDigest !== storedRequestDigest)
|
|
548
|
+
throw new SessionProtocolError("completion_mismatch");
|
|
549
|
+
return { duplicate: true, outcome: existing.outcome };
|
|
550
|
+
}
|
|
551
|
+
if (stored) {
|
|
552
|
+
for (const reference of stored.messageReferences) {
|
|
553
|
+
const sequence = await tx.get(key(`message-id:${encodeURIComponent(reference.id)}`));
|
|
554
|
+
const entry = sequence === undefined
|
|
555
|
+
? undefined
|
|
556
|
+
: await getSessionPayload(tx, sequenceKey("message", sequence));
|
|
557
|
+
if (!entry ||
|
|
558
|
+
(await digestAgentSessionMessage(entry.message)) !==
|
|
559
|
+
reference.sha256)
|
|
560
|
+
throw new SessionProtocolError("stored_message_mismatch", 412);
|
|
561
|
+
input.messages.push(entry.message);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
const inputDigest = standardDigest ?? (await digest(input));
|
|
515
565
|
if (existing) {
|
|
516
566
|
if (existing.digest !== inputDigest)
|
|
517
567
|
throw new SessionProtocolError("completion_mismatch");
|
|
@@ -566,7 +616,7 @@ export async function handleAgentSessionExecutionRequest(request, storage) {
|
|
|
566
616
|
if (input.context && input.context.runId !== input.runId)
|
|
567
617
|
throw new SessionProtocolError("context_run_mismatch");
|
|
568
618
|
const previousRevision = await readRevision(tx);
|
|
569
|
-
await writeValues(tx, input);
|
|
619
|
+
await writeValues(tx, stored ? { ...input, messages: [] } : input);
|
|
570
620
|
const modelCalls = await tx.get(key(`model-calls:${encodeURIComponent(input.runId)}`));
|
|
571
621
|
const outcome = {
|
|
572
622
|
...(modelCalls === undefined
|
|
@@ -586,6 +636,7 @@ export async function handleAgentSessionExecutionRequest(request, storage) {
|
|
|
586
636
|
await tx.put(receiptKey, {
|
|
587
637
|
outcome,
|
|
588
638
|
digest: inputDigest,
|
|
639
|
+
...(storedRequestDigest ? { storedRequestDigest } : {}),
|
|
589
640
|
});
|
|
590
641
|
const rangeKey = key(`run-events:${encodeURIComponent(input.runId)}`);
|
|
591
642
|
const storedRange = await tx.get(rangeKey);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { agentSessionInboxFinishSchema } from "@gea-ai/contract/agent-session-inbox";
|
|
2
3
|
import type { AgentChildRun } from "@gea-ai/contract/agent-worker-tools";
|
|
3
4
|
import type { AgentSessionStorage, AgentSessionTransactionStorage } from "./agent-session";
|
|
4
5
|
/** Only private definitions/self copies share the owning Agent's business context. */
|
|
@@ -47,6 +48,8 @@ export declare function queueAgentSessionChildRun(tx: AgentSessionTransactionSto
|
|
|
47
48
|
export declare function acceptAgentSessionChildRun(tx: AgentSessionTransactionStorage, runId: string): Promise<void>;
|
|
48
49
|
/** Called in the child-run transaction. Progress is recorded by the run store only. */
|
|
49
50
|
export declare function notifyAgentSessionChildRun(tx: AgentSessionTransactionStorage, run: AgentChildRun): Promise<void>;
|
|
51
|
+
/** Called inside the managed lease handoff transaction. */
|
|
52
|
+
export declare function finishAgentSessionInbox(tx: AgentSessionTransactionStorage, input: z.infer<typeof agentSessionInboxFinishSchema>, runId: string): Promise<Response>;
|
|
50
53
|
/** Private Session protocol. Ingress must authenticate initialization and sender provenance. */
|
|
51
54
|
export declare function handleAgentSessionInboxRequest(request: Request, storage: AgentSessionStorage): Promise<Response | undefined>;
|
|
52
55
|
/** Lazy Durable Object entrypoint. A pending dispatch retries with the same execution ID. */
|
|
@@ -3,7 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { agentSessionExecutionTurnSchema } from "@gea-ai/contract/agent-session";
|
|
4
4
|
import { agentApplicationAssistantMessageSchema } from "@gea-ai/contract/agent-applications";
|
|
5
5
|
import { canonicalAgentChannelJson } from "@gea-ai/contract/agent-channels";
|
|
6
|
-
import { agentSessionIdentitySchema, agentSessionBootstrapSchema, agentSessionTransportSchema, agentSessionDispatchSchema, agentSessionInboxExecutionSchema, agentSessionSendSchema, agentRunWaitInputSchema, agentRunWaitResultSchema, toAgentRunWaitOutput, } from "@gea-ai/contract/agent-session-inbox";
|
|
6
|
+
import { agentSessionIdentitySchema, agentSessionBootstrapSchema, agentSessionTransportSchema, agentSessionDispatchSchema, agentSessionInboxFinishSchema, agentSessionInboxExecutionSchema, agentSessionSendSchema, agentRunWaitInputSchema, agentRunWaitResultSchema, toAgentRunWaitOutput, } from "@gea-ai/contract/agent-session-inbox";
|
|
7
7
|
import { combineTraceContexts, readTraceContext } from "./trace-context.js";
|
|
8
8
|
import { traceContextSchema } from "@gea-ai/contract/traces";
|
|
9
9
|
import { activeComputerOperation } from "./agent-session-computer.js";
|
|
@@ -408,6 +408,88 @@ export async function notifyAgentSessionChildRun(tx, run) {
|
|
|
408
408
|
}
|
|
409
409
|
await save(tx, state, runs);
|
|
410
410
|
}
|
|
411
|
+
async function finishInbox(tx, state, runs, input) {
|
|
412
|
+
if (!state.execution || state.execution.id !== input.executionId)
|
|
413
|
+
return Response.json({ error: "execution_mismatch" }, { status: 409 });
|
|
414
|
+
let { outcome, inputWait, requireExpiredLease } = input;
|
|
415
|
+
if (requireExpiredLease) {
|
|
416
|
+
const stored = await tx.get("execution-v2:active-turn");
|
|
417
|
+
const turn = stored === undefined
|
|
418
|
+
? null
|
|
419
|
+
: agentSessionExecutionTurnSchema.parse(stored);
|
|
420
|
+
if (outcome?.status !== "interrupted" ||
|
|
421
|
+
turn?.runId !== state.execution.turnId ||
|
|
422
|
+
turn.status !== "running" ||
|
|
423
|
+
Date.parse(turn.leaseExpiresAt) > Date.now()) {
|
|
424
|
+
await save(tx, state, runs);
|
|
425
|
+
return Response.json({ error: "execution_not_expired" }, { status: 409 });
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
if (inputWait && !outcome && !state.stopped)
|
|
429
|
+
state.inputWait = {
|
|
430
|
+
turnId: state.execution.turnId,
|
|
431
|
+
messageId: inputWait.messageId,
|
|
432
|
+
};
|
|
433
|
+
// A transport disconnect does not revoke already accepted child runs.
|
|
434
|
+
if (outcome?.status === "cancelled" &&
|
|
435
|
+
!state.stopped &&
|
|
436
|
+
runs.some((run) => !run.outcome)) {
|
|
437
|
+
state.wait = {
|
|
438
|
+
turnId: state.execution.turnId,
|
|
439
|
+
toolCallId: null,
|
|
440
|
+
runIds: runs.filter((run) => !run.outcome).map((run) => run.id),
|
|
441
|
+
deadline: null,
|
|
442
|
+
};
|
|
443
|
+
outcome = undefined;
|
|
444
|
+
}
|
|
445
|
+
if (outcome && outcome.status !== "completed" && state.wait) {
|
|
446
|
+
if (state.wait.toolCallId)
|
|
447
|
+
state.resumeWait = waitResult(state, runs, true);
|
|
448
|
+
state.wait = null;
|
|
449
|
+
}
|
|
450
|
+
if (outcome && outcome.status !== "completed") {
|
|
451
|
+
state.stopped = true;
|
|
452
|
+
for (const run of runs)
|
|
453
|
+
if (!run.outcome) {
|
|
454
|
+
run.cancelRequested = true;
|
|
455
|
+
await putChildRun(tx, run);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if ((outcome || state.stopped) && state.transport) {
|
|
459
|
+
const terminal = {
|
|
460
|
+
execution: { ...state.execution, messages: [], waitResult: null },
|
|
461
|
+
outcome: outcome ?? {
|
|
462
|
+
status: "cancelled",
|
|
463
|
+
text: "Run cancelled.",
|
|
464
|
+
},
|
|
465
|
+
transport: state.transport,
|
|
466
|
+
};
|
|
467
|
+
if (runs.some((run) => !run.outcome))
|
|
468
|
+
state.draining = terminal;
|
|
469
|
+
else if (state.terminal)
|
|
470
|
+
state.terminalQueue.push(terminal);
|
|
471
|
+
else
|
|
472
|
+
state.terminal = terminal;
|
|
473
|
+
}
|
|
474
|
+
state.execution = null;
|
|
475
|
+
state.started = false;
|
|
476
|
+
await save(tx, state, runs);
|
|
477
|
+
return Response.json({
|
|
478
|
+
finished: true,
|
|
479
|
+
waiting: state.wait !== null || state.inputWait !== null,
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
/** Called inside the managed lease handoff transaction. */
|
|
483
|
+
export async function finishAgentSessionInbox(tx, input, runId) {
|
|
484
|
+
if (!tx.setAlarm || !tx.deleteAlarm)
|
|
485
|
+
return Response.json({ error: "session_alarms_unavailable" }, { status: 503 });
|
|
486
|
+
const state = await readState(tx);
|
|
487
|
+
if (!state?.execution ||
|
|
488
|
+
state.execution.id !== input.executionId ||
|
|
489
|
+
state.execution.turnId !== runId)
|
|
490
|
+
return Response.json({ error: "execution_mismatch" }, { status: 409 });
|
|
491
|
+
return finishInbox(tx, state, await readInboxChildRuns(tx, state), input);
|
|
492
|
+
}
|
|
411
493
|
/** Private Session protocol. Ingress must authenticate initialization and sender provenance. */
|
|
412
494
|
export async function handleAgentSessionInboxRequest(request, storage) {
|
|
413
495
|
const path = new URL(request.url).pathname;
|
|
@@ -770,89 +852,18 @@ export async function handleAgentSessionInboxRequest(request, storage) {
|
|
|
770
852
|
return Response.json(result ? { status: "ready", result } : { status: "suspended" });
|
|
771
853
|
}
|
|
772
854
|
if (finishing) {
|
|
773
|
-
|
|
774
|
-
.extend({
|
|
775
|
-
outcome: agentSessionDispatchSchema.shape.outcome,
|
|
776
|
-
requireExpiredLease: z.literal(true).optional(),
|
|
777
|
-
inputWait: z
|
|
778
|
-
.strictObject({ messageId: z.string().min(1) })
|
|
779
|
-
.optional(),
|
|
780
|
-
})
|
|
855
|
+
const input = agentSessionInboxFinishSchema
|
|
856
|
+
.extend({ requireExpiredLease: z.literal(true).optional() })
|
|
781
857
|
.parse(rejecting
|
|
782
858
|
? {
|
|
783
|
-
executionId:
|
|
859
|
+
executionId: state.execution.id,
|
|
784
860
|
outcome: {
|
|
785
861
|
status: "failed",
|
|
786
862
|
text: "Session dispatch authorization was denied.",
|
|
787
863
|
},
|
|
788
864
|
}
|
|
789
865
|
: body);
|
|
790
|
-
|
|
791
|
-
const stored = await tx.get("execution-v2:active-turn");
|
|
792
|
-
const turn = stored === undefined
|
|
793
|
-
? null
|
|
794
|
-
: agentSessionExecutionTurnSchema.parse(stored);
|
|
795
|
-
if (outcome?.status !== "interrupted" ||
|
|
796
|
-
turn?.runId !== state.execution.turnId ||
|
|
797
|
-
turn.status !== "running" ||
|
|
798
|
-
Date.parse(turn.leaseExpiresAt) > Date.now()) {
|
|
799
|
-
await save(tx, state, runs);
|
|
800
|
-
return Response.json({ error: "execution_not_expired" }, { status: 409 });
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
if (inputWait && !outcome && !state.stopped)
|
|
804
|
-
state.inputWait = {
|
|
805
|
-
turnId: state.execution.turnId,
|
|
806
|
-
messageId: inputWait.messageId,
|
|
807
|
-
};
|
|
808
|
-
// A transport disconnect does not revoke already accepted child runs.
|
|
809
|
-
if (outcome?.status === "cancelled" &&
|
|
810
|
-
!state.stopped &&
|
|
811
|
-
runs.some((run) => !run.outcome)) {
|
|
812
|
-
state.wait = {
|
|
813
|
-
turnId: state.execution.turnId,
|
|
814
|
-
toolCallId: null,
|
|
815
|
-
runIds: runs.filter((run) => !run.outcome).map((run) => run.id),
|
|
816
|
-
deadline: null,
|
|
817
|
-
};
|
|
818
|
-
outcome = undefined;
|
|
819
|
-
}
|
|
820
|
-
if (outcome && outcome.status !== "completed" && state.wait) {
|
|
821
|
-
if (state.wait.toolCallId)
|
|
822
|
-
state.resumeWait = waitResult(state, runs, true);
|
|
823
|
-
state.wait = null;
|
|
824
|
-
}
|
|
825
|
-
if (outcome && outcome.status !== "completed") {
|
|
826
|
-
state.stopped = true;
|
|
827
|
-
for (const run of runs)
|
|
828
|
-
if (!run.outcome) {
|
|
829
|
-
run.cancelRequested = true;
|
|
830
|
-
await putChildRun(tx, run);
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
if ((outcome || state.stopped) && state.transport) {
|
|
834
|
-
const terminal = {
|
|
835
|
-
execution: { ...state.execution, messages: [], waitResult: null },
|
|
836
|
-
outcome: outcome ?? {
|
|
837
|
-
status: "cancelled",
|
|
838
|
-
text: "Run cancelled.",
|
|
839
|
-
},
|
|
840
|
-
transport: state.transport,
|
|
841
|
-
};
|
|
842
|
-
if (runs.some((run) => !run.outcome))
|
|
843
|
-
state.draining = terminal;
|
|
844
|
-
else if (state.terminal)
|
|
845
|
-
state.terminalQueue.push(terminal);
|
|
846
|
-
else
|
|
847
|
-
state.terminal = terminal;
|
|
848
|
-
}
|
|
849
|
-
state.execution = null;
|
|
850
|
-
state.started = false;
|
|
851
|
-
await save(tx, state, runs);
|
|
852
|
-
return Response.json({
|
|
853
|
-
finished: true,
|
|
854
|
-
waiting: state.wait !== null || state.inputWait !== null,
|
|
855
|
-
});
|
|
866
|
+
return finishInbox(tx, state, runs, input);
|
|
856
867
|
}
|
|
857
868
|
const messages = takeMessages(state, true);
|
|
858
869
|
state.messages = state.messages.filter((entry) => !messages.includes(entry));
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { type AgentSessionMessage } from "@gea-ai/contract/agent-session";
|
|
1
2
|
/** Both hosted and in-Worker clients use the same bounded, non-retrying transport. */
|
|
2
3
|
export declare function sendAgentSessionRequest(input: {
|
|
3
4
|
path: string;
|
|
4
5
|
body?: unknown;
|
|
5
6
|
send: (path: string, body: string | undefined) => Promise<Response>;
|
|
6
7
|
}): Promise<Response>;
|
|
8
|
+
/** Compare message content independently of object-key insertion order. JSON
|
|
9
|
+
* serialization also gives Dates the same representation as persisted values. */
|
|
10
|
+
export declare function digestAgentSessionMessage(message: AgentSessionMessage): Promise<string>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AGENT_SESSION_TRANSFER_CHARS, agentSessionExecutionMutationSchema, agentSessionExecutionPauseSchema, agentSessionExecutionCompleteSchema, MAX_AGENT_SESSION_EXECUTION_BODY_BYTES, MAX_AGENT_SESSION_TRANSFER_BYTES, agentSessionAttemptIdentitySchema, agentSessionTransferIdentitySchema, } from "@gea-ai/contract/agent-session";
|
|
1
|
+
import { AGENT_SESSION_TRANSFER_CHARS, agentSessionExecutionMutationSchema, agentSessionMessageSchema, agentSessionExecutionPauseSchema, agentSessionExecutionCompleteSchema, MAX_AGENT_SESSION_EXECUTION_BODY_BYTES, MAX_AGENT_SESSION_TRANSFER_BYTES, agentSessionAttemptIdentitySchema, agentSessionTransferIdentitySchema, } from "@gea-ai/contract/agent-session";
|
|
2
2
|
/** Both hosted and in-Worker clients use the same bounded, non-retrying transport. */
|
|
3
3
|
export async function sendAgentSessionRequest(input) {
|
|
4
4
|
const serialized = input.body === undefined ? undefined : JSON.stringify(input.body);
|
|
@@ -49,3 +49,11 @@ export async function sendAgentSessionRequest(input) {
|
|
|
49
49
|
}
|
|
50
50
|
return input.send(input.path, JSON.stringify({ $agentSessionTransfer: { ...identity, chunks } }));
|
|
51
51
|
}
|
|
52
|
+
/** Compare message content independently of object-key insertion order. JSON
|
|
53
|
+
* serialization also gives Dates the same representation as persisted values. */
|
|
54
|
+
export async function digestAgentSessionMessage(message) {
|
|
55
|
+
const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify(agentSessionMessageSchema.parse(message), (_key, value) => value !== null && typeof value === "object" && !Array.isArray(value)
|
|
56
|
+
? Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0))
|
|
57
|
+
: value)));
|
|
58
|
+
return Array.from(new Uint8Array(bytes), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
59
|
+
}
|
|
@@ -4,3 +4,9 @@ import type { UIMessageChunk } from "ai";
|
|
|
4
4
|
* Control events, tool results and part boundaries retain their exact order.
|
|
5
5
|
*/
|
|
6
6
|
export declare function coalesceAgentTextDeltas<T extends UIMessageChunk>(stream: ReadableStream<T>): ReadableStream<T>;
|
|
7
|
+
/** Group ready events for one durable mutation. Start/finish stay separate so
|
|
8
|
+
* first bytes are immediate and context settlement cannot hold back prior text.
|
|
9
|
+
* At most 5 ms, 64 events or 64 Ki serialized characters are collected. An
|
|
10
|
+
* oversized individual event uses the existing Session transfer protocol.
|
|
11
|
+
*/
|
|
12
|
+
export declare function batchAgentStreamEvents<T extends UIMessageChunk>(stream: ReadableStream<T>): ReadableStream<T[]>;
|
|
@@ -71,3 +71,86 @@ export function coalesceAgentTextDeltas(stream) {
|
|
|
71
71
|
},
|
|
72
72
|
});
|
|
73
73
|
}
|
|
74
|
+
/** Group ready events for one durable mutation. Start/finish stay separate so
|
|
75
|
+
* first bytes are immediate and context settlement cannot hold back prior text.
|
|
76
|
+
* At most 5 ms, 64 events or 64 Ki serialized characters are collected. An
|
|
77
|
+
* oversized individual event uses the existing Session transfer protocol.
|
|
78
|
+
*/
|
|
79
|
+
export function batchAgentStreamEvents(stream) {
|
|
80
|
+
const reader = stream.getReader();
|
|
81
|
+
let pending;
|
|
82
|
+
let held;
|
|
83
|
+
let failure;
|
|
84
|
+
let cancelled = false;
|
|
85
|
+
const read = () => (pending ??= reader.read());
|
|
86
|
+
const boundary = (chunk) => chunk.type === "start" ||
|
|
87
|
+
chunk.type === "finish" ||
|
|
88
|
+
chunk.type === "abort" ||
|
|
89
|
+
chunk.type === "error";
|
|
90
|
+
return new ReadableStream({
|
|
91
|
+
async pull(controller) {
|
|
92
|
+
let timer;
|
|
93
|
+
try {
|
|
94
|
+
if (failure)
|
|
95
|
+
throw failure.error;
|
|
96
|
+
const first = held ?? (await read());
|
|
97
|
+
held = undefined;
|
|
98
|
+
pending = undefined;
|
|
99
|
+
if (cancelled)
|
|
100
|
+
return;
|
|
101
|
+
if (first.done) {
|
|
102
|
+
reader.releaseLock();
|
|
103
|
+
controller.close();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const batch = [first.value];
|
|
107
|
+
let size = JSON.stringify(first.value).length;
|
|
108
|
+
if (!boundary(first.value) && size < 64 * 1024) {
|
|
109
|
+
const deadline = new Promise((resolve) => {
|
|
110
|
+
timer = setTimeout(() => resolve(null), 5);
|
|
111
|
+
});
|
|
112
|
+
while (batch.length < 64 && size < 64 * 1024) {
|
|
113
|
+
let next;
|
|
114
|
+
try {
|
|
115
|
+
next = await Promise.race([read(), deadline]);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
// Received events still precede the failure in the durable journal.
|
|
119
|
+
failure = { error };
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
if (next === null)
|
|
123
|
+
break;
|
|
124
|
+
pending = undefined;
|
|
125
|
+
if (next.done || boundary(next.value)) {
|
|
126
|
+
held = next;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
const nextSize = JSON.stringify(next.value).length;
|
|
130
|
+
if (size + nextSize > 64 * 1024) {
|
|
131
|
+
held = next;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
batch.push(next.value);
|
|
135
|
+
size += nextSize;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (!cancelled)
|
|
139
|
+
controller.enqueue(batch);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
if (!cancelled)
|
|
143
|
+
controller.error(error);
|
|
144
|
+
reader.releaseLock();
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
clearTimeout(timer);
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
async cancel(reason) {
|
|
151
|
+
cancelled = true;
|
|
152
|
+
await reader.cancel(reason);
|
|
153
|
+
reader.releaseLock();
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|
package/dist/agent-worker.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AGENT_SUBAGENT_CONCURRENCY_HEADER } from "@gea-ai/contract/agent-worker-tools";
|
|
2
2
|
import { DEFAULT_MAX_CONCURRENT_SUBAGENTS } from "@gea-ai/contract/agent-packages";
|
|
3
|
-
import { coalesceAgentTextDeltas } from "./agent-stream-coalescing.js";
|
|
3
|
+
import { batchAgentStreamEvents, coalesceAgentTextDeltas, } from "./agent-stream-coalescing.js";
|
|
4
4
|
import { sendAgentSessionRequest } from "./agent-session-transport.js";
|
|
5
5
|
import { agentSessionOverridesSchema } from "@gea-ai/contract/agent-session-overrides";
|
|
6
6
|
import { resolveModelOptions } from "@gea-ai/contract/model-options";
|
|
@@ -13,7 +13,6 @@ export { createGeaAI } from "./gea-ai.js";
|
|
|
13
13
|
import { mergeLlmJson, llmRuntimeRequestOptionsSchema, } from "@gea-ai/contract/llm-catalog";
|
|
14
14
|
import { executeChannelAgentRun, uploadChannelInput, } from "./agent-channel-api.js";
|
|
15
15
|
export { extractChannelReply } from "./agent-channel-api.js";
|
|
16
|
-
import { agentWorkerAttachmentOutputSchema } from "@gea-ai/contract/agent-api-host";
|
|
17
16
|
import { streamManagedAgentSessionRun } from "./agent-session-stream.js";
|
|
18
17
|
import { agentSessionReadToolOutputSchema, agentSessionReadToolOutputResultSchema, } from "@gea-ai/contract/agent-session";
|
|
19
18
|
import { handleAgentComputerRequest, readSessionComputer, } from "./agent-computer-http.js";
|
|
@@ -32,7 +31,7 @@ import { agentModelCallSchema, } from "@gea-ai/contract/agent-usage";
|
|
|
32
31
|
import { createAgentContextRuntime } from "./context-runtime.js";
|
|
33
32
|
import { studioExternalUserPrincipalSchema } from "@gea-ai/contract";
|
|
34
33
|
import { replaceInputFiles, withTurnImages } from "./agent-worker-attachments.js";
|
|
35
|
-
import { toAgentHttpFile
|
|
34
|
+
import { toAgentHttpFile } from "@gea-ai/contract/agent-http-api";
|
|
36
35
|
import { handleHostedAgentHttp } from "./agent-http.js";
|
|
37
36
|
import { createWorkerAgentTool, createWorkerRunCancelTool } from "./agent-call.js";
|
|
38
37
|
import { AgentSessionRunner, resumeSessionWait } from "./agent-session-runner.js";
|
|
@@ -1602,7 +1601,6 @@ function createAgentWorkerHostAdapter(bindings) {
|
|
|
1602
1601
|
}
|
|
1603
1602
|
if (path === `${HOST_PATH}/tools/initialize` ||
|
|
1604
1603
|
path === `${HOST_PATH}/tools/attachments/read` ||
|
|
1605
|
-
path === `${HOST_PATH}/tools/attachments/outputs` ||
|
|
1606
1604
|
path === `${HOST_PATH}/tools/attachments/prepare` ||
|
|
1607
1605
|
path === `${HOST_PATH}/tools/invoke` ||
|
|
1608
1606
|
path === `${HOST_PATH}/agents/runs` ||
|
|
@@ -2709,6 +2707,7 @@ export async function runAgentWorkerRequest(input) {
|
|
|
2709
2707
|
}, async (signal, waitUntil) => {
|
|
2710
2708
|
const invocationTraceContext = captureTraceContext();
|
|
2711
2709
|
const invocationSpan = trace.getActiveSpan();
|
|
2710
|
+
const invocationTiming = {};
|
|
2712
2711
|
const cancellation = new AbortController();
|
|
2713
2712
|
let stopComputer;
|
|
2714
2713
|
let inboxOutcome = {
|
|
@@ -2748,7 +2747,8 @@ export async function runAgentWorkerRequest(input) {
|
|
|
2748
2747
|
clearTimeout(cancellationTimer);
|
|
2749
2748
|
if (leaseMaintenance)
|
|
2750
2749
|
await leaseMaintenance;
|
|
2751
|
-
|
|
2750
|
+
const leaseDrainedAt = performance.now();
|
|
2751
|
+
if (input.sessionExecution && !input.inboxExecution) {
|
|
2752
2752
|
results.push(...(await Promise.allSettled([
|
|
2753
2753
|
requestSessionJson({
|
|
2754
2754
|
body: { ...input.sessionExecution, leaseOwner: "host" },
|
|
@@ -2764,33 +2764,62 @@ export async function runAgentWorkerRequest(input) {
|
|
|
2764
2764
|
"gea.session.finish.background_ms": Math.round(drainedAt - cleanupStartedAt),
|
|
2765
2765
|
"gea.session.finish.computer_stop_ms": Math.round(computerStoppedAt - drainedAt),
|
|
2766
2766
|
"gea.session.finish.session_release_ms": Math.round(sessionFinishedAt - computerStoppedAt),
|
|
2767
|
-
"gea.session.finish.
|
|
2767
|
+
"gea.session.finish.lease_observer_wait_ms": Math.round(leaseDrainedAt - sessionFinishedAt),
|
|
2768
|
+
"gea.session.finish.lease_handoff_ms": Math.round(performance.now() - leaseDrainedAt),
|
|
2768
2769
|
});
|
|
2769
2770
|
if (input.inboxExecution) {
|
|
2771
|
+
const inboxStartedAt = performance.now();
|
|
2770
2772
|
const status = cancellation.signal.aborted
|
|
2771
2773
|
? "cancelled"
|
|
2772
2774
|
: results.some((result) => result.status === "rejected")
|
|
2773
2775
|
? "failed"
|
|
2774
2776
|
: inboxOutcome.status;
|
|
2775
2777
|
invocationSpan?.setAttribute("gea.session.status", status);
|
|
2778
|
+
const inbox = {
|
|
2779
|
+
executionId: input.inboxExecution.id,
|
|
2780
|
+
...(status === "waiting"
|
|
2781
|
+
? { inputWait: inboxOutcome.inputWait }
|
|
2782
|
+
: {
|
|
2783
|
+
outcome: {
|
|
2784
|
+
status,
|
|
2785
|
+
text: inboxOutcome.text.slice(0, 200_000),
|
|
2786
|
+
traceContext: invocationTraceContext,
|
|
2787
|
+
},
|
|
2788
|
+
}),
|
|
2789
|
+
};
|
|
2790
|
+
// Managed execution and Inbox are on the same Session object. Seal
|
|
2791
|
+
// the Inbox and hand its lease back in one fenced durable commit.
|
|
2792
|
+
const releasePath = input.sessionExecution
|
|
2793
|
+
? "/v2/turns/release"
|
|
2794
|
+
: "/v1/inbox/finish";
|
|
2776
2795
|
const sessionResponse = await input.sessionNamespace
|
|
2777
2796
|
.get(input.sessionNamespace.idFromName(sessionChatId))
|
|
2778
|
-
.fetch(new Request(
|
|
2797
|
+
.fetch(new Request(`https://agent-session.internal${releasePath}`, {
|
|
2779
2798
|
method: "POST",
|
|
2780
2799
|
headers: { "content-type": "application/json" },
|
|
2781
|
-
body: JSON.stringify(
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2800
|
+
body: JSON.stringify(input.sessionExecution
|
|
2801
|
+
? { ...input.sessionExecution, inbox }
|
|
2802
|
+
: inbox),
|
|
2803
|
+
}))
|
|
2804
|
+
.finally(() => invocationSpan?.setAttribute("gea.session.finish.inbox_ms", Math.round(performance.now() - inboxStartedAt)));
|
|
2805
|
+
for (const [metric, attribute] of [
|
|
2806
|
+
["session-handler", "gea.session.finish.inbox_handler_ms"],
|
|
2807
|
+
[
|
|
2808
|
+
"session-transaction",
|
|
2809
|
+
"gea.session.finish.inbox_transaction_ms",
|
|
2810
|
+
],
|
|
2811
|
+
]) {
|
|
2812
|
+
const timing = sessionResponse.headers
|
|
2813
|
+
.get("server-timing")
|
|
2814
|
+
?.split(",")
|
|
2815
|
+
.map((entry) => entry.trim())
|
|
2816
|
+
.find((entry) => entry.startsWith(`${metric};dur=`));
|
|
2817
|
+
if (timing) {
|
|
2818
|
+
const duration = Number(timing.slice(`${metric};dur=`.length));
|
|
2819
|
+
if (Number.isFinite(duration) && duration >= 0)
|
|
2820
|
+
invocationSpan?.setAttribute(attribute, duration);
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2794
2823
|
if (!sessionResponse.ok)
|
|
2795
2824
|
throw new Error(`Session finish rejected (${sessionResponse.status}).`);
|
|
2796
2825
|
}
|
|
@@ -2873,7 +2902,7 @@ export async function runAgentWorkerRequest(input) {
|
|
|
2873
2902
|
stopComputer = stop;
|
|
2874
2903
|
}, (outcome) => {
|
|
2875
2904
|
inboxOutcome = outcome;
|
|
2876
|
-
});
|
|
2905
|
+
}, invocationTiming);
|
|
2877
2906
|
}
|
|
2878
2907
|
catch (error) {
|
|
2879
2908
|
cancellation.abort(error);
|
|
@@ -2917,6 +2946,8 @@ export async function runAgentWorkerRequest(input) {
|
|
|
2917
2946
|
}
|
|
2918
2947
|
signal.removeEventListener("abort", onAbort);
|
|
2919
2948
|
await finish();
|
|
2949
|
+
if (invocationTiming.lastModelFinishedAt !== undefined)
|
|
2950
|
+
invocationSpan?.setAttribute("gea.session.finish.model_to_close_ms", Math.round(performance.now() - invocationTiming.lastModelFinishedAt));
|
|
2920
2951
|
controller.close();
|
|
2921
2952
|
}
|
|
2922
2953
|
catch (error) {
|
|
@@ -2938,7 +2969,7 @@ export async function runAgentWorkerRequest(input) {
|
|
|
2938
2969
|
});
|
|
2939
2970
|
});
|
|
2940
2971
|
}
|
|
2941
|
-
async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessionOutcome) {
|
|
2972
|
+
async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessionOutcome, invocationTiming) {
|
|
2942
2973
|
const invocationTrace = activeTraceRef();
|
|
2943
2974
|
const host = input.bindings
|
|
2944
2975
|
? createAgentWorkerHostAdapter(input.bindings)
|
|
@@ -2968,7 +2999,13 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
2968
2999
|
let writeTail = Promise.resolve();
|
|
2969
3000
|
let writeFailure;
|
|
2970
3001
|
const persistenceSpan = trace.getActiveSpan();
|
|
2971
|
-
const persistenceTotals = {
|
|
3002
|
+
const persistenceTotals = {
|
|
3003
|
+
writes: 0,
|
|
3004
|
+
queueMs: 0,
|
|
3005
|
+
requestMs: 0,
|
|
3006
|
+
events: 0,
|
|
3007
|
+
eventBatches: 0,
|
|
3008
|
+
};
|
|
2972
3009
|
if (sessionExecution) {
|
|
2973
3010
|
if (sessionExecution.runId !== payload.runId)
|
|
2974
3011
|
throw new Error("Session Run identity mismatch.");
|
|
@@ -3009,10 +3046,16 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
3009
3046
|
finally {
|
|
3010
3047
|
const requestMs = performance.now() - startedAt;
|
|
3011
3048
|
persistenceTotals.writes++;
|
|
3049
|
+
if (values.events?.length) {
|
|
3050
|
+
persistenceTotals.events += values.events.length;
|
|
3051
|
+
persistenceTotals.eventBatches++;
|
|
3052
|
+
}
|
|
3012
3053
|
persistenceTotals.queueMs += queueMs;
|
|
3013
3054
|
persistenceTotals.requestMs += requestMs;
|
|
3014
3055
|
persistenceSpan?.setAttributes({
|
|
3015
3056
|
"gea.session.persistence.writes": persistenceTotals.writes,
|
|
3057
|
+
"gea.session.persistence.events": persistenceTotals.events,
|
|
3058
|
+
"gea.session.persistence.event_batches": persistenceTotals.eventBatches,
|
|
3016
3059
|
"gea.session.persistence.queue_ms": Math.round(persistenceTotals.queueMs),
|
|
3017
3060
|
"gea.session.persistence.request_ms": Math.round(persistenceTotals.requestMs),
|
|
3018
3061
|
});
|
|
@@ -3832,6 +3875,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
3832
3875
|
let mergedResponseMessageCount = 0;
|
|
3833
3876
|
let contextCheckpoint;
|
|
3834
3877
|
let reusedContextCheckpoints = 0;
|
|
3878
|
+
let modelCallsInitialized = false;
|
|
3835
3879
|
const persistModelContext = () => {
|
|
3836
3880
|
const modelContext = {
|
|
3837
3881
|
contextSize: context.contextSize,
|
|
@@ -3848,7 +3892,10 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
3848
3892
|
return contextCheckpoint.pending;
|
|
3849
3893
|
}
|
|
3850
3894
|
const pending = sessionExecution
|
|
3851
|
-
? mutateSession({
|
|
3895
|
+
? mutateSession({
|
|
3896
|
+
context: modelContext,
|
|
3897
|
+
...(!modelCallsInitialized ? { modelCalls: [] } : {}),
|
|
3898
|
+
})
|
|
3852
3899
|
: requestSessionJson({
|
|
3853
3900
|
body: { context: modelContext },
|
|
3854
3901
|
chatId: payload.chatId,
|
|
@@ -3857,6 +3904,8 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
3857
3904
|
path: "/v1/model-context",
|
|
3858
3905
|
schema: agentSessionPutModelContextResultSchema,
|
|
3859
3906
|
}).then(() => undefined);
|
|
3907
|
+
if (sessionExecution)
|
|
3908
|
+
modelCallsInitialized = true;
|
|
3860
3909
|
contextCheckpoint = { serialized, pending };
|
|
3861
3910
|
return pending;
|
|
3862
3911
|
};
|
|
@@ -3902,8 +3951,10 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
3902
3951
|
await appendStreamEvent({ type: "data-model-call", data: call }, `model-call:${call.callId}`);
|
|
3903
3952
|
}
|
|
3904
3953
|
};
|
|
3905
|
-
if (sessionExecution)
|
|
3954
|
+
if (sessionExecution && !modelCallsInitialized) {
|
|
3906
3955
|
await mutateSession({ modelCalls: [] });
|
|
3956
|
+
modelCallsInitialized = true;
|
|
3957
|
+
}
|
|
3907
3958
|
let contextSignal = input.request.signal;
|
|
3908
3959
|
const contextRuntime = createAgentContextRuntime({
|
|
3909
3960
|
ai: host,
|
|
@@ -4073,12 +4124,16 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
4073
4124
|
catch (error) {
|
|
4074
4125
|
fail(error);
|
|
4075
4126
|
}
|
|
4127
|
+
const finalContextStartedAt = performance.now();
|
|
4076
4128
|
try {
|
|
4077
4129
|
await persistModelContext();
|
|
4078
4130
|
}
|
|
4079
4131
|
catch (error) {
|
|
4080
4132
|
fail(error);
|
|
4081
4133
|
}
|
|
4134
|
+
finally {
|
|
4135
|
+
persistenceSpan?.setAttribute("gea.session.finish.final_context_ms", Math.round(performance.now() - finalContextStartedAt));
|
|
4136
|
+
}
|
|
4082
4137
|
resolveContext({
|
|
4083
4138
|
...(!failure && inboxRunner?.suspended
|
|
4084
4139
|
? { sessionStatus: "waiting" }
|
|
@@ -4095,52 +4150,10 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
4095
4150
|
})();
|
|
4096
4151
|
return settling;
|
|
4097
4152
|
};
|
|
4098
|
-
const publishedArtifacts = new Map();
|
|
4099
|
-
const emittedArtifacts = new Set();
|
|
4100
|
-
const refreshArtifacts = async () => {
|
|
4101
|
-
if (!artifactScope || input.request.signal.aborted)
|
|
4102
|
-
return;
|
|
4103
|
-
let cursor;
|
|
4104
|
-
do {
|
|
4105
|
-
const response = await host.fetch(`${HOST_PATH}/tools/attachments/outputs`, {
|
|
4106
|
-
method: "POST",
|
|
4107
|
-
headers: { "content-type": "application/json" },
|
|
4108
|
-
body: JSON.stringify({ scope: artifactScope, cursor }),
|
|
4109
|
-
signal: input.request.signal,
|
|
4110
|
-
});
|
|
4111
|
-
if (!response.ok)
|
|
4112
|
-
throw new Error(`Could not list published Artifacts (${response.status}).`);
|
|
4113
|
-
const page = z
|
|
4114
|
-
.object({
|
|
4115
|
-
items: z.array(agentWorkerAttachmentOutputSchema),
|
|
4116
|
-
next_cursor: z.uuid().nullable(),
|
|
4117
|
-
})
|
|
4118
|
-
.parse(await response.json());
|
|
4119
|
-
for (const artifact of page.items)
|
|
4120
|
-
publishedArtifacts.set(artifact.id, artifact);
|
|
4121
|
-
cursor = page.next_cursor ?? undefined;
|
|
4122
|
-
} while (cursor);
|
|
4123
|
-
};
|
|
4124
|
-
let finalArtifacts;
|
|
4125
|
-
const refreshFinalArtifacts = () =>
|
|
4126
|
-
// Context callbacks can publish outputs. Both terminal consumers use the
|
|
4127
|
-
// same snapshot, taken only after those callbacks and their work settle.
|
|
4128
|
-
(finalArtifacts ??= contextFinished.then(() => refreshArtifacts()));
|
|
4129
4153
|
const saveResponseMessage = async ({ responseMessage, }) => {
|
|
4130
4154
|
const saveStartedAt = performance.now();
|
|
4131
4155
|
const metadata = await contextFinished;
|
|
4132
4156
|
const contextFinishedAt = performance.now();
|
|
4133
|
-
await refreshFinalArtifacts();
|
|
4134
|
-
const artifactsFinishedAt = performance.now();
|
|
4135
|
-
responseMessage.parts = [
|
|
4136
|
-
...responseMessage.parts.filter((part) => part.type !== "data-artifact" ||
|
|
4137
|
-
!publishedArtifacts.has(part.data.id)),
|
|
4138
|
-
...[...publishedArtifacts.values()].map((data) => ({
|
|
4139
|
-
type: "data-artifact",
|
|
4140
|
-
id: data.id,
|
|
4141
|
-
data,
|
|
4142
|
-
})),
|
|
4143
|
-
];
|
|
4144
4157
|
if (inboxRunner?.suspendedToolCallId) {
|
|
4145
4158
|
responseMessage.parts = responseMessage.parts.map((part) => {
|
|
4146
4159
|
if (isToolUIPart(part) &&
|
|
@@ -4178,7 +4191,6 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
4178
4191
|
await persistModelContext();
|
|
4179
4192
|
const contextPersistedAt = performance.now();
|
|
4180
4193
|
persistenceSpan?.setAttributes({
|
|
4181
|
-
"gea.session.finish.artifacts_ms": Math.round(artifactsFinishedAt - contextFinishedAt),
|
|
4182
4194
|
"gea.session.finish.context_wait_ms": Math.round(contextFinishedAt - saveStartedAt),
|
|
4183
4195
|
"gea.session.finish.transcript_ms": Math.round(transcriptFinishedAt - transcriptStartedAt),
|
|
4184
4196
|
"gea.session.finish.context_write_ms": Math.round(contextPersistedAt - transcriptFinishedAt),
|
|
@@ -4517,6 +4529,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
4517
4529
|
: part.finish_reason;
|
|
4518
4530
|
const usage = coreUsage(part.usage);
|
|
4519
4531
|
mainUsage = addAgentWorkerUsage(mainUsage, usage);
|
|
4532
|
+
invocationTiming.lastModelFinishedAt = performance.now();
|
|
4520
4533
|
await recordMainCall("completed", usage);
|
|
4521
4534
|
modelSpan?.setAttribute("gen_ai.output.messages", JSON.stringify(toModelMessages([part.message])));
|
|
4522
4535
|
if (usage.inputTokens !== undefined)
|
|
@@ -4607,6 +4620,9 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
4607
4620
|
throw new Error("Agent Worker model is missing.");
|
|
4608
4621
|
const result = streamText({
|
|
4609
4622
|
telemetry: agentModelTelemetry,
|
|
4623
|
+
onLanguageModelCallEnd() {
|
|
4624
|
+
invocationTiming.lastModelFinishedAt = performance.now();
|
|
4625
|
+
},
|
|
4610
4626
|
onError: ({ error }) => {
|
|
4611
4627
|
fail(error);
|
|
4612
4628
|
},
|
|
@@ -4722,89 +4738,84 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
4722
4738
|
},
|
|
4723
4739
|
});
|
|
4724
4740
|
}
|
|
4725
|
-
const durableStream = (sessionExecution
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
chatId: payload.chatId,
|
|
4741
|
-
namespace: input.sessionNamespace,
|
|
4742
|
-
path: "/v1/messages",
|
|
4743
|
-
});
|
|
4744
|
-
for (const event of events)
|
|
4745
|
-
await appendStreamEvent(event);
|
|
4746
|
-
}
|
|
4747
|
-
}
|
|
4748
|
-
if (chunk.type === "start" && responseBaseline) {
|
|
4749
|
-
// Each internal invocation still has its own start/finish for accounting.
|
|
4750
|
-
// The public journal represents one response across those invocations.
|
|
4751
|
-
controller.enqueue(chunk);
|
|
4752
|
-
const result = input.inboxExecution?.waitResult;
|
|
4753
|
-
if (result) {
|
|
4754
|
-
const output = {
|
|
4755
|
-
type: "tool-output-available",
|
|
4756
|
-
toolCallId: result.toolCallId,
|
|
4757
|
-
output: toAgentRunWaitOutput(result),
|
|
4758
|
-
};
|
|
4759
|
-
if (sessionExecution)
|
|
4760
|
-
await mutateSession({ events: [output] });
|
|
4761
|
-
else
|
|
4762
|
-
await appendStreamEvent(output);
|
|
4763
|
-
controller.enqueue(output);
|
|
4764
|
-
}
|
|
4765
|
-
return;
|
|
4766
|
-
}
|
|
4767
|
-
if (chunk.type === "tool-output-available" &&
|
|
4768
|
-
chunk.toolCallId === inboxRunner?.suspendedToolCallId)
|
|
4769
|
-
return;
|
|
4770
|
-
if (chunk.type === "finish-step" || chunk.type === "finish") {
|
|
4771
|
-
if (chunk.type === "finish")
|
|
4772
|
-
await refreshFinalArtifacts();
|
|
4773
|
-
else
|
|
4774
|
-
await refreshArtifacts();
|
|
4775
|
-
for (const data of publishedArtifacts.values()) {
|
|
4776
|
-
if (emittedArtifacts.has(data.id))
|
|
4777
|
-
continue;
|
|
4778
|
-
const event = { type: "data-artifact", id: data.id, data };
|
|
4741
|
+
const durableStream = (sessionExecution
|
|
4742
|
+
? batchAgentStreamEvents(coalesceAgentTextDeltas(stream))
|
|
4743
|
+
: stream.pipeThrough(new TransformStream({
|
|
4744
|
+
transform(chunk, controller) {
|
|
4745
|
+
controller.enqueue([chunk]);
|
|
4746
|
+
},
|
|
4747
|
+
}))).pipeThrough(new TransformStream({
|
|
4748
|
+
async transform(chunks, controller) {
|
|
4749
|
+
const events = [];
|
|
4750
|
+
for (let chunk of chunks) {
|
|
4751
|
+
if (chunk.type === "start" && interruptedResponse) {
|
|
4752
|
+
const events = [
|
|
4753
|
+
...(interruptedWait ? [interruptedWait] : []),
|
|
4754
|
+
{ type: "finish", finishReason: "other" },
|
|
4755
|
+
];
|
|
4779
4756
|
if (sessionExecution)
|
|
4780
|
-
await mutateSession({
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4757
|
+
await mutateSession({
|
|
4758
|
+
messages: [{ ...interruptedResponse }],
|
|
4759
|
+
events,
|
|
4760
|
+
});
|
|
4761
|
+
else {
|
|
4762
|
+
await appendSessionValue({
|
|
4763
|
+
body: { message: interruptedResponse, runId: payload.runId },
|
|
4764
|
+
chatId: payload.chatId,
|
|
4765
|
+
namespace: input.sessionNamespace,
|
|
4766
|
+
path: "/v1/messages",
|
|
4767
|
+
});
|
|
4768
|
+
for (const event of events)
|
|
4769
|
+
await appendStreamEvent(event);
|
|
4770
|
+
}
|
|
4785
4771
|
}
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
if (chunk.type === "finish") {
|
|
4790
|
-
const metadata = await contextFinished;
|
|
4791
|
-
chunk = {
|
|
4792
|
-
...chunk,
|
|
4793
|
-
finishReason: metadata.finishReason,
|
|
4794
|
-
messageMetadata: metadata,
|
|
4795
|
-
};
|
|
4796
|
-
if (metadata.sessionStatus === "waiting") {
|
|
4772
|
+
if (chunk.type === "start" && responseBaseline) {
|
|
4773
|
+
// Each internal invocation still has its own start/finish for accounting.
|
|
4774
|
+
// The public journal represents one response across those invocations.
|
|
4797
4775
|
controller.enqueue(chunk);
|
|
4798
|
-
|
|
4776
|
+
const result = input.inboxExecution?.waitResult;
|
|
4777
|
+
if (result) {
|
|
4778
|
+
const output = {
|
|
4779
|
+
type: "tool-output-available",
|
|
4780
|
+
toolCallId: result.toolCallId,
|
|
4781
|
+
output: toAgentRunWaitOutput(result),
|
|
4782
|
+
};
|
|
4783
|
+
if (sessionExecution)
|
|
4784
|
+
await mutateSession({ events: [output] });
|
|
4785
|
+
else
|
|
4786
|
+
await appendStreamEvent(output);
|
|
4787
|
+
controller.enqueue(output);
|
|
4788
|
+
}
|
|
4789
|
+
continue;
|
|
4799
4790
|
}
|
|
4791
|
+
if (chunk.type === "tool-output-available" &&
|
|
4792
|
+
chunk.toolCallId === inboxRunner?.suspendedToolCallId)
|
|
4793
|
+
continue;
|
|
4794
|
+
// AI SDK emits finish before awaiting onEnd. Publish success and usage
|
|
4795
|
+
// only after the context strategy and its registered work have settled.
|
|
4796
|
+
if (chunk.type === "finish") {
|
|
4797
|
+
const metadata = await contextFinished;
|
|
4798
|
+
chunk = {
|
|
4799
|
+
...chunk,
|
|
4800
|
+
finishReason: metadata.finishReason,
|
|
4801
|
+
messageMetadata: metadata,
|
|
4802
|
+
};
|
|
4803
|
+
if (metadata.sessionStatus === "waiting") {
|
|
4804
|
+
controller.enqueue(chunk);
|
|
4805
|
+
continue;
|
|
4806
|
+
}
|
|
4807
|
+
}
|
|
4808
|
+
events.push(chunk);
|
|
4800
4809
|
}
|
|
4801
|
-
if (
|
|
4802
|
-
await mutateSession({ events: [chunk] });
|
|
4803
|
-
controller.enqueue(chunk);
|
|
4810
|
+
if (events.length === 0)
|
|
4804
4811
|
return;
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4812
|
+
if (sessionExecution)
|
|
4813
|
+
await mutateSession({ events });
|
|
4814
|
+
else
|
|
4815
|
+
for (const event of events)
|
|
4816
|
+
await appendStreamEvent(event);
|
|
4817
|
+
for (const event of events)
|
|
4818
|
+
controller.enqueue(event);
|
|
4808
4819
|
},
|
|
4809
4820
|
}));
|
|
4810
4821
|
if (engine) {
|
package/dist/context-runtime.js
CHANGED
|
@@ -195,21 +195,53 @@ export function createAgentContextRuntime(input) {
|
|
|
195
195
|
});
|
|
196
196
|
},
|
|
197
197
|
async onEnd(stepNumber, end, runCallback = true) {
|
|
198
|
+
const endSpan = createAgentTracing().currentSpan;
|
|
198
199
|
const finalize = async () => {
|
|
200
|
+
const startedAt = performance.now();
|
|
201
|
+
let backgroundMs = 0;
|
|
202
|
+
let callbackMs = 0;
|
|
203
|
+
let compactMs = 0;
|
|
199
204
|
try {
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
await
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
205
|
+
const compactStartedAt = performance.now();
|
|
206
|
+
try {
|
|
207
|
+
const updated = await compact(false);
|
|
208
|
+
if (descriptor.type === "default" || offload !== false)
|
|
209
|
+
await persist(updated);
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
compactMs = performance.now() - compactStartedAt;
|
|
213
|
+
}
|
|
214
|
+
const backgroundStartedAt = performance.now();
|
|
215
|
+
try {
|
|
216
|
+
await settle();
|
|
217
|
+
}
|
|
218
|
+
finally {
|
|
219
|
+
backgroundMs += performance.now() - backgroundStartedAt;
|
|
220
|
+
}
|
|
221
|
+
if (runCallback && strategy.onEnd) {
|
|
222
|
+
const callbackStartedAt = performance.now();
|
|
223
|
+
try {
|
|
224
|
+
await invoke(stepNumber, (event) => strategy.onEnd({ ...event, ...end }));
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
callbackMs = performance.now() - callbackStartedAt;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
206
230
|
}
|
|
207
231
|
finally {
|
|
232
|
+
const backgroundStartedAt = performance.now();
|
|
208
233
|
try {
|
|
209
234
|
await settle();
|
|
210
235
|
}
|
|
211
236
|
finally {
|
|
237
|
+
backgroundMs += performance.now() - backgroundStartedAt;
|
|
212
238
|
closed = true;
|
|
239
|
+
endSpan?.setAttributes({
|
|
240
|
+
"gea.session.finish.context_compact_ms": Math.round(compactMs),
|
|
241
|
+
"gea.session.finish.context_background_ms": Math.round(backgroundMs),
|
|
242
|
+
"gea.session.finish.context_callback_ms": Math.round(callbackMs),
|
|
243
|
+
"gea.session.finish.context_total_ms": Math.round(performance.now() - startedAt),
|
|
244
|
+
});
|
|
213
245
|
}
|
|
214
246
|
}
|
|
215
247
|
};
|
|
@@ -13,7 +13,7 @@ export async function* projectAgentCoreEvents(turn, options) {
|
|
|
13
13
|
let resumed = false;
|
|
14
14
|
const blocks = new Map();
|
|
15
15
|
const calls = new Map();
|
|
16
|
-
|
|
16
|
+
let lastModelHadToolCalls = false;
|
|
17
17
|
let outcome;
|
|
18
18
|
try {
|
|
19
19
|
yield {
|
|
@@ -38,7 +38,7 @@ export async function* projectAgentCoreEvents(turn, options) {
|
|
|
38
38
|
case "step_resumed":
|
|
39
39
|
step = event.step;
|
|
40
40
|
resumed = true;
|
|
41
|
-
|
|
41
|
+
lastModelHadToolCalls = true;
|
|
42
42
|
break;
|
|
43
43
|
case "step_started":
|
|
44
44
|
resumed = false;
|
|
@@ -113,11 +113,7 @@ export async function* projectAgentCoreEvents(turn, options) {
|
|
|
113
113
|
break;
|
|
114
114
|
}
|
|
115
115
|
case "model_finished":
|
|
116
|
-
|
|
117
|
-
step: event.step,
|
|
118
|
-
finish_reason: event.finish_reason,
|
|
119
|
-
usage: event.usage,
|
|
120
|
-
});
|
|
116
|
+
lastModelHadToolCalls = event.finish_reason === "tool_calls";
|
|
121
117
|
break;
|
|
122
118
|
case "approval_requested": {
|
|
123
119
|
const call = calls.get(event.call_id);
|
|
@@ -237,12 +233,7 @@ export async function* projectAgentCoreEvents(turn, options) {
|
|
|
237
233
|
if (chunk)
|
|
238
234
|
yield chunk;
|
|
239
235
|
}
|
|
240
|
-
const messageMetadata = {
|
|
241
|
-
...(outcome.output === undefined ? {} : { output: outcome.output }),
|
|
242
|
-
agentCore: { reason: outcome.reason, steps },
|
|
243
|
-
};
|
|
244
236
|
if (outcome.reason === "cancelled") {
|
|
245
|
-
yield { type: "message-metadata", messageMetadata };
|
|
246
237
|
yield { type: "abort", reason: "cancelled" };
|
|
247
238
|
return;
|
|
248
239
|
}
|
|
@@ -259,7 +250,7 @@ export async function* projectAgentCoreEvents(turn, options) {
|
|
|
259
250
|
yield {
|
|
260
251
|
type: "finish",
|
|
261
252
|
finishReason: outcome.reason === "stopped"
|
|
262
|
-
?
|
|
253
|
+
? lastModelHadToolCalls
|
|
263
254
|
? "tool-calls"
|
|
264
255
|
: "stop"
|
|
265
256
|
: outcome.reason === "completed"
|
|
@@ -271,7 +262,9 @@ export async function* projectAgentCoreEvents(turn, options) {
|
|
|
271
262
|
outcome.reason === "awaiting_tool_results"
|
|
272
263
|
? "tool-calls"
|
|
273
264
|
: "error",
|
|
274
|
-
|
|
265
|
+
...(outcome.output === undefined
|
|
266
|
+
? {}
|
|
267
|
+
: { messageMetadata: { output: outcome.output } }),
|
|
275
268
|
};
|
|
276
269
|
}
|
|
277
270
|
export function createAgentCoreUIStream(core, message, host, options) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gea-ai/agent-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.260923-alpha.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"homepage": "https://musegea.com/developers",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"@ai-sdk/otel": "1.0.9",
|
|
109
109
|
"@ai-sdk/provider": "4.0.1",
|
|
110
110
|
"@ai-sdk/provider-utils": "5.0.2",
|
|
111
|
-
"@gea-ai/contract": "0.1.
|
|
111
|
+
"@gea-ai/contract": "0.1.260923-alpha.0",
|
|
112
112
|
"@opentelemetry/api": "1.9.1",
|
|
113
113
|
"@opentelemetry/context-async-hooks": "2.10.0",
|
|
114
114
|
"@opentelemetry/core": "2.10.0",
|