@gea-ai/agent-sdk 0.1.260925-alpha.0 → 0.1.261002-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-channel-author-runtime.d.ts +13 -13
- package/dist/agent-core.js +3 -0
- package/dist/agent-message-history.d.ts +3 -0
- package/dist/agent-message-history.js +38 -0
- package/dist/agent-remote-run.js +104 -50
- package/dist/agent-run-host.d.ts +1 -1
- package/dist/agent-run-host.js +16 -7
- package/dist/agent-session-execution.js +18 -1
- package/dist/agent-session-inbox.js +21 -0
- package/dist/agent-worker.js +28 -22
- package/dist/experimental/agent-core-ui.d.ts +8 -0
- package/dist/experimental/agent-core-ui.js +14 -3
- package/dist/experimental/agent-core.d.ts +9 -0
- package/dist/experimental/agent-core.js +1 -0
- package/dist/index.d.ts +1 -11
- package/dist/index.js +3 -3
- package/dist/tool-model-output.d.ts +7 -1
- package/dist/tool-model-output.js +4 -8
- package/dist/tracing.js +6 -3
- package/dist/version.d.ts +1 -0
- package/dist/version.js +2 -0
- package/package.json +3 -3
|
@@ -19,19 +19,6 @@ export declare function executeChannelBehavior(input: {
|
|
|
19
19
|
environment: AgentChannelFetchEnvironment;
|
|
20
20
|
signal: AbortSignal;
|
|
21
21
|
}): Promise<{
|
|
22
|
-
sequence: number;
|
|
23
|
-
duplicate: boolean;
|
|
24
|
-
} | {
|
|
25
|
-
state: AgentChannelJson;
|
|
26
|
-
intents: {
|
|
27
|
-
key: string;
|
|
28
|
-
payload: AgentChannelJson;
|
|
29
|
-
eventId: string;
|
|
30
|
-
operationId: string;
|
|
31
|
-
}[];
|
|
32
|
-
cursor: number;
|
|
33
|
-
idempotencyWindowMs?: number | undefined;
|
|
34
|
-
} | {
|
|
35
22
|
status: "sent";
|
|
36
23
|
receipt?: AgentChannelJson | undefined;
|
|
37
24
|
} | {
|
|
@@ -56,6 +43,19 @@ export declare function executeChannelBehavior(input: {
|
|
|
56
43
|
message: string;
|
|
57
44
|
retryable: boolean;
|
|
58
45
|
};
|
|
46
|
+
} | {
|
|
47
|
+
sequence: number;
|
|
48
|
+
duplicate: boolean;
|
|
49
|
+
} | {
|
|
50
|
+
state: AgentChannelJson;
|
|
51
|
+
intents: {
|
|
52
|
+
key: string;
|
|
53
|
+
payload: AgentChannelJson;
|
|
54
|
+
eventId: string;
|
|
55
|
+
operationId: string;
|
|
56
|
+
}[];
|
|
57
|
+
cursor: number;
|
|
58
|
+
idempotencyWindowMs?: number | undefined;
|
|
59
59
|
} | {
|
|
60
60
|
events: {
|
|
61
61
|
id: string;
|
package/dist/agent-core.js
CHANGED
|
@@ -7,6 +7,9 @@ let compiled;
|
|
|
7
7
|
export function agentCore(options = {}) {
|
|
8
8
|
return {
|
|
9
9
|
...options,
|
|
10
|
+
// A silent model stream must not retain a Run until the provider hard deadline.
|
|
11
|
+
// Explicit timeout options (including {}) retain caller-owned policy.
|
|
12
|
+
timeout: options.timeout ?? { chunkMs: 5 * 60_000 },
|
|
10
13
|
output: options.output === undefined
|
|
11
14
|
? undefined
|
|
12
15
|
: toAgentCoreOutput(options.output),
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const assistantHistorySchema = z
|
|
3
|
+
.object({
|
|
4
|
+
role: z.literal("assistant"),
|
|
5
|
+
parts: z.array(z.unknown()),
|
|
6
|
+
})
|
|
7
|
+
.passthrough();
|
|
8
|
+
const legacyDenialSchema = z
|
|
9
|
+
.object({
|
|
10
|
+
type: z.union([z.literal("dynamic-tool"), z.string().startsWith("tool-")]),
|
|
11
|
+
toolCallId: z.string(),
|
|
12
|
+
state: z.literal("output-denied"),
|
|
13
|
+
approval: z
|
|
14
|
+
.object({ id: z.string(), approved: z.literal(true) })
|
|
15
|
+
.passthrough()
|
|
16
|
+
.optional(),
|
|
17
|
+
})
|
|
18
|
+
.passthrough();
|
|
19
|
+
/** Old Core policy denials lacked the rejected approval required by AI SDK.
|
|
20
|
+
* Repair only their presentation; persisted Core decisions still own execution. */
|
|
21
|
+
export function repairLegacyToolDenials(message) {
|
|
22
|
+
const history = assistantHistorySchema.safeParse(message);
|
|
23
|
+
if (!history.success)
|
|
24
|
+
return message;
|
|
25
|
+
return {
|
|
26
|
+
...history.data,
|
|
27
|
+
parts: history.data.parts.map((part) => {
|
|
28
|
+
const denied = legacyDenialSchema.safeParse(part);
|
|
29
|
+
return denied.success
|
|
30
|
+
? {
|
|
31
|
+
...denied.data,
|
|
32
|
+
state: "output-error",
|
|
33
|
+
errorText: "Tool execution was denied by policy.",
|
|
34
|
+
}
|
|
35
|
+
: part;
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
38
|
+
}
|
package/dist/agent-remote-run.js
CHANGED
|
@@ -57,60 +57,114 @@ export async function cancelRemoteRun(fetch, run) {
|
|
|
57
57
|
const response = await fetch(`/runs/${run.id}/cancel`, { method: "POST" });
|
|
58
58
|
await responseJson(response);
|
|
59
59
|
}
|
|
60
|
+
class RemoteRunReadError extends TaggedError("RemoteRunReadError")() {
|
|
61
|
+
}
|
|
62
|
+
async function waitForRemoteRead(ms, signal) {
|
|
63
|
+
signal.throwIfAborted();
|
|
64
|
+
await new Promise((resolve, reject) => {
|
|
65
|
+
const abort = () => {
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
reject(signal.reason);
|
|
68
|
+
};
|
|
69
|
+
const timer = setTimeout(() => {
|
|
70
|
+
signal.removeEventListener("abort", abort);
|
|
71
|
+
resolve();
|
|
72
|
+
}, ms);
|
|
73
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
74
|
+
if (signal.aborted)
|
|
75
|
+
abort();
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/** Only idempotent reads retry. Admission and Tool execution are never repeated here. */
|
|
79
|
+
async function readRemoteRunPage(fetch, path, signal) {
|
|
80
|
+
for (let attempt = 0;; attempt++) {
|
|
81
|
+
signal.throwIfAborted();
|
|
82
|
+
const response = await Result.tryPromise(() => fetch(path, { signal }));
|
|
83
|
+
signal.throwIfAborted();
|
|
84
|
+
let status;
|
|
85
|
+
let delay = 250 * 2 ** attempt;
|
|
86
|
+
if (response.isOk()) {
|
|
87
|
+
if (response.value.ok)
|
|
88
|
+
return response.value.json();
|
|
89
|
+
status = response.value.status;
|
|
90
|
+
const retryAfter = response.value.headers.get("retry-after");
|
|
91
|
+
if (retryAfter) {
|
|
92
|
+
const requestedDelay = /^\d+$/u.test(retryAfter)
|
|
93
|
+
? Number(retryAfter) * 1000
|
|
94
|
+
: Date.parse(retryAfter) - Date.now();
|
|
95
|
+
if (Number.isFinite(requestedDelay))
|
|
96
|
+
delay = Math.max(delay, requestedDelay);
|
|
97
|
+
}
|
|
98
|
+
await response.value.body?.cancel();
|
|
99
|
+
}
|
|
100
|
+
if (attempt >= 2 ||
|
|
101
|
+
delay > 5000 ||
|
|
102
|
+
(status !== undefined && status !== 408 && status !== 429 && status < 500))
|
|
103
|
+
throw new RemoteRunReadError({
|
|
104
|
+
message: "Remote Run read failed.",
|
|
105
|
+
status,
|
|
106
|
+
});
|
|
107
|
+
await waitForRemoteRead(delay, signal);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
60
110
|
export async function observeRemoteRun(fetch, run, signal, onInputRequired) {
|
|
61
111
|
let reportedInput;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
112
|
+
try {
|
|
113
|
+
while (true) {
|
|
114
|
+
signal.throwIfAborted();
|
|
115
|
+
const record = agentHttpRunSchema.parse(await readRemoteRunPage(fetch, `/runs/${run.id}`, signal));
|
|
116
|
+
if (record.id !== run.id || record.session_id !== run.sessionId)
|
|
117
|
+
throw new Error("Remote Run identity mismatch.");
|
|
118
|
+
const terminal = ["finished", "error", "aborted", "interrupted"].includes(record.status);
|
|
119
|
+
const inputMessageId = record.status === "waiting" ? record.input_message_id : null;
|
|
120
|
+
if (terminal || (onInputRequired && inputMessageId)) {
|
|
121
|
+
const text = [];
|
|
122
|
+
let cursor = null;
|
|
123
|
+
do {
|
|
124
|
+
const page = messagesSchema.parse(await readRemoteRunPage(fetch, `/sessions/${run.sessionId}/messages?limit=100${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""}`, signal));
|
|
125
|
+
for (const message of page.items)
|
|
126
|
+
if (message.run_id === run.id && message.role === "assistant") {
|
|
127
|
+
if (!terminal &&
|
|
128
|
+
message.id === inputMessageId &&
|
|
129
|
+
onInputRequired) {
|
|
130
|
+
const input = agentApplicationAssistantMessageSchema.parse({
|
|
131
|
+
id: message.id,
|
|
132
|
+
role: message.role,
|
|
133
|
+
parts: message.parts,
|
|
134
|
+
});
|
|
135
|
+
const key = agentInputRequestKey(input);
|
|
136
|
+
if (key !== reportedInput) {
|
|
137
|
+
await onInputRequired(input);
|
|
138
|
+
reportedInput = key;
|
|
139
|
+
}
|
|
86
140
|
}
|
|
141
|
+
for (const part of message.parts)
|
|
142
|
+
if (part.type === "text" && typeof part.text === "string")
|
|
143
|
+
text.push(part.text);
|
|
87
144
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
145
|
+
cursor = page.next_cursor;
|
|
146
|
+
} while (cursor);
|
|
147
|
+
if (terminal)
|
|
148
|
+
return {
|
|
149
|
+
status: record.status,
|
|
150
|
+
text: text.join("\n").slice(0, 200_000),
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
if (!["queued", "running", "waiting"].includes(record.status))
|
|
154
|
+
throw new Error("Unknown remote Run status.");
|
|
155
|
+
await waitForRemoteRead(1000, signal);
|
|
99
156
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
},
|
|
111
|
-
|
|
112
|
-
if (signal.aborted)
|
|
113
|
-
abort();
|
|
114
|
-
});
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
// Stop only this parent-owned observation. The remote execution/directory is untouched.
|
|
160
|
+
// Host shutdown leaves the relation pending so another dispatcher can resume it.
|
|
161
|
+
signal.throwIfAborted();
|
|
162
|
+
const status = RemoteRunReadError.is(error) && error.status
|
|
163
|
+
? ` (HTTP ${error.status})`
|
|
164
|
+
: "";
|
|
165
|
+
return {
|
|
166
|
+
status: "interrupted",
|
|
167
|
+
text: `Remote observation interrupted${status}; execution outcome is unknown. Run ${run.id}, Session ${run.sessionId}. Do not resubmit the task; inspect this existing Run.`,
|
|
168
|
+
};
|
|
115
169
|
}
|
|
116
170
|
}
|
package/dist/agent-run-host.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export declare class AgentRunHost {
|
|
|
20
20
|
response: Response;
|
|
21
21
|
dispose?: () => void;
|
|
22
22
|
}>;
|
|
23
|
-
cancelChild(run: AgentChildRun): Promise<void>;
|
|
23
|
+
cancelChild(run: AgentChildRun): Promise<boolean | void>;
|
|
24
24
|
onError(error: unknown, identity?: {
|
|
25
25
|
runId?: string;
|
|
26
26
|
sessionId?: string;
|
package/dist/agent-run-host.js
CHANGED
|
@@ -161,9 +161,7 @@ export class AgentRunHost {
|
|
|
161
161
|
dispatch(run, inboxExecution) {
|
|
162
162
|
if (this.closed)
|
|
163
163
|
throw new Error("Run dispatcher is stopping.");
|
|
164
|
-
if (this.executions.has(run.id) ||
|
|
165
|
-
run.outcome ||
|
|
166
|
-
(run.cancelRequested && run.destination.type !== "remote"))
|
|
164
|
+
if (this.executions.has(run.id) || run.outcome)
|
|
167
165
|
return;
|
|
168
166
|
const cancellation = new AbortController();
|
|
169
167
|
this.executions.set(run.id, cancellation);
|
|
@@ -207,8 +205,18 @@ export class AgentRunHost {
|
|
|
207
205
|
});
|
|
208
206
|
}
|
|
209
207
|
else {
|
|
210
|
-
cancellation.
|
|
211
|
-
|
|
208
|
+
// Keep admission alive until the Session has received cancellation.
|
|
209
|
+
// Aborting the request earlier can strand an uninitialized child.
|
|
210
|
+
const cancelled = await Result.tryPromise(() => this.host.cancelChild(run));
|
|
211
|
+
if (cancelled.isOk() && cancelled.value !== false) {
|
|
212
|
+
cancellation.abort();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (cancelled.isErr())
|
|
216
|
+
this.host.onError(cancelled.error, {
|
|
217
|
+
runId: run.id,
|
|
218
|
+
sessionId: run.sessionId,
|
|
219
|
+
});
|
|
212
220
|
}
|
|
213
221
|
}
|
|
214
222
|
timer = setTimeout(() => {
|
|
@@ -284,8 +292,9 @@ export class AgentRunHost {
|
|
|
284
292
|
this.dispatch(run);
|
|
285
293
|
}
|
|
286
294
|
else {
|
|
287
|
-
this.
|
|
288
|
-
await this.host.cancelChild(run)
|
|
295
|
+
this.dispatch(run);
|
|
296
|
+
if ((await this.host.cancelChild(run)) !== false)
|
|
297
|
+
this.executions.get(runId)?.abort();
|
|
289
298
|
}
|
|
290
299
|
}
|
|
291
300
|
return Response.json({
|
|
@@ -444,6 +444,15 @@ export async function handleAgentSessionExecutionRequest(request, storage) {
|
|
|
444
444
|
handlerMs = performance.now() - handlerStartedAt;
|
|
445
445
|
return response;
|
|
446
446
|
});
|
|
447
|
+
console.info("AgentSession lease release completed", {
|
|
448
|
+
event: "agent.session.lease_released",
|
|
449
|
+
runId: input.runId,
|
|
450
|
+
attemptId: input.attemptId,
|
|
451
|
+
executionId: input.inbox.executionId,
|
|
452
|
+
inputWait: Boolean(input.inbox.inputWait),
|
|
453
|
+
status: response.status,
|
|
454
|
+
elapsedMs: Math.round(performance.now() - startedAt),
|
|
455
|
+
});
|
|
447
456
|
response.headers.set("server-timing", `session-handler;dur=${handlerMs.toFixed(1)}, session-transaction;dur=${(performance.now() - startedAt).toFixed(1)}`);
|
|
448
457
|
return response;
|
|
449
458
|
}
|
|
@@ -683,7 +692,15 @@ export async function handleAgentSessionExecutionRequest(request, storage) {
|
|
|
683
692
|
.parse(await readBody(request, storage));
|
|
684
693
|
const result = await storage.transaction(async (tx) => {
|
|
685
694
|
const active = await tx.get(key("active-turn"));
|
|
686
|
-
if (!active
|
|
695
|
+
if (!active) {
|
|
696
|
+
// A local logical Run can execute or wait without a managed lease. The
|
|
697
|
+
// inbox still owns its descendants and fences cancellation by Run ID.
|
|
698
|
+
const state = await getAgentSessionRunState(tx, input.runId);
|
|
699
|
+
return {
|
|
700
|
+
requested: state?.status === "waiting" || state?.status === "running",
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
if (active.runId !== input.runId)
|
|
687
704
|
return { requested: false };
|
|
688
705
|
if (!active.cancelRequestedAt) {
|
|
689
706
|
const turn = {
|
|
@@ -467,6 +467,20 @@ async function finishInbox(tx, state, runs, input) {
|
|
|
467
467
|
await save(tx, state, runs);
|
|
468
468
|
return Response.json({ error: "execution_not_expired" }, { status: 409 });
|
|
469
469
|
}
|
|
470
|
+
console.warn("AgentSession execution lease expired", {
|
|
471
|
+
event: "agent.session.lease_expired",
|
|
472
|
+
sessionId: state.identity.sessionId,
|
|
473
|
+
runId: turn.runId,
|
|
474
|
+
attemptId: turn.attemptId,
|
|
475
|
+
requestId: turn.requestId,
|
|
476
|
+
executionId: state.execution.id,
|
|
477
|
+
leaseOwner: turn.leaseOwner,
|
|
478
|
+
leaseExpiresAt: turn.leaseExpiresAt,
|
|
479
|
+
started: state.started,
|
|
480
|
+
waiting: state.wait !== null,
|
|
481
|
+
pendingChildren: runs.filter((run) => !run.outcome).length,
|
|
482
|
+
pendingStarts: state.pendingStarts.length,
|
|
483
|
+
});
|
|
470
484
|
}
|
|
471
485
|
if (inputWait && !outcome && !state.stopped) {
|
|
472
486
|
state.inputWait = {
|
|
@@ -644,6 +658,13 @@ export async function handleAgentSessionInboxRequest(request, storage) {
|
|
|
644
658
|
if (previous !== undefined)
|
|
645
659
|
return Response.json({ error: "message_already_accepted" }, { status: 409 });
|
|
646
660
|
if (input.message && state.inputWait) {
|
|
661
|
+
// Retried dispatch of this Run is not a new user turn. In particular,
|
|
662
|
+
// a late child-start command must not skip the input it just requested.
|
|
663
|
+
if (state.inputWait.turnId === (input.runId ?? input.executionId))
|
|
664
|
+
return Response.json({ error: "execution_already_started" }, {
|
|
665
|
+
status: 409,
|
|
666
|
+
headers: { "x-gea-session-execution": "already-started" },
|
|
667
|
+
});
|
|
647
668
|
await skipInputWait(tx, state, runs);
|
|
648
669
|
if (state.draining)
|
|
649
670
|
return Response.json({ error: "session_busy" }, { status: 409 });
|
package/dist/agent-worker.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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
3
|
import { batchAgentStreamEvents, coalesceAgentTextDeltas, } from "./agent-stream-coalescing.js";
|
|
4
|
+
import { repairLegacyToolDenials } from "./agent-message-history.js";
|
|
4
5
|
import { sendAgentSessionRequest } from "./agent-session-transport.js";
|
|
5
6
|
import { agentSessionOverridesSchema } from "@gea-ai/contract/agent-session-overrides";
|
|
6
7
|
import { resolveModelOptions } from "@gea-ai/contract/model-options";
|
|
@@ -3146,13 +3147,19 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
3146
3147
|
const submittedUser = payload.currentTurn.some((message) => message.role === "user")
|
|
3147
3148
|
? inputUser
|
|
3148
3149
|
: undefined;
|
|
3149
|
-
if (input.inboxExecution && submittedUser)
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3150
|
+
if (input.inboxExecution && submittedUser) {
|
|
3151
|
+
if (sessionExecution) {
|
|
3152
|
+
await mutateSession({ messages: [{ ...submittedUser }] });
|
|
3153
|
+
}
|
|
3154
|
+
else {
|
|
3155
|
+
await appendSessionValue({
|
|
3156
|
+
body: { message: submittedUser, runId: payload.runId },
|
|
3157
|
+
chatId: payload.chatId,
|
|
3158
|
+
namespace: input.sessionNamespace,
|
|
3159
|
+
path: "/v1/messages",
|
|
3160
|
+
});
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3156
3163
|
// Tool continuations still need the current user's durable file references
|
|
3157
3164
|
// when reconstructing model messages from saved UI history.
|
|
3158
3165
|
const submittedFiles = inputUser?.parts.flatMap((part) => (part.type === "file" ? [part] : [])) ??
|
|
@@ -3615,7 +3622,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
3615
3622
|
schema: z.object({ message: z.unknown() }),
|
|
3616
3623
|
});
|
|
3617
3624
|
const [message] = await validateUIMessages({
|
|
3618
|
-
messages: [saved.message],
|
|
3625
|
+
messages: [repairLegacyToolDenials(saved.message)],
|
|
3619
3626
|
});
|
|
3620
3627
|
if (!message || message.role !== "assistant")
|
|
3621
3628
|
throw new Error("Suspended response is not an assistant message.");
|
|
@@ -3740,14 +3747,9 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
3740
3747
|
.filter((file) => file.mediaType.startsWith("image/"))
|
|
3741
3748
|
.map(({ url: _url, ...file }) => file);
|
|
3742
3749
|
const turnImages = new Map();
|
|
3743
|
-
const
|
|
3744
|
-
const reference = file.id ?? file.key;
|
|
3745
|
-
if (turnImages.has(reference))
|
|
3746
|
-
return;
|
|
3750
|
+
const readImage = async (file) => {
|
|
3747
3751
|
if (!artifactScope)
|
|
3748
3752
|
throw new Error("Images require Agent Artifacts.");
|
|
3749
|
-
if (turnImages.size >= MAX_AGENT_ARTIFACT_IMAGES)
|
|
3750
|
-
throw new Error(`At most ${MAX_AGENT_ARTIFACT_IMAGES} images can be viewed in one user turn.`);
|
|
3751
3753
|
const response = await host.fetch(`${HOST_PATH}/tools/attachments/read`, {
|
|
3752
3754
|
method: "POST",
|
|
3753
3755
|
headers: { "content-type": "application/json" },
|
|
@@ -3762,11 +3764,19 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
3762
3764
|
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
3763
3765
|
if (bytes.byteLength > MAX_AGENT_ARTIFACT_IMAGE_BYTES)
|
|
3764
3766
|
throw new Error("Prepared image exceeds the model image limit.");
|
|
3765
|
-
|
|
3767
|
+
return {
|
|
3766
3768
|
type: "file",
|
|
3767
3769
|
data: { type: "data", data: bytes },
|
|
3768
3770
|
mediaType: response.headers.get("content-type") ?? file.mediaType,
|
|
3769
|
-
}
|
|
3771
|
+
};
|
|
3772
|
+
};
|
|
3773
|
+
const addImage = async (file) => {
|
|
3774
|
+
const reference = file.id ?? file.key;
|
|
3775
|
+
if (turnImages.has(reference))
|
|
3776
|
+
return;
|
|
3777
|
+
if (turnImages.size >= MAX_AGENT_ARTIFACT_IMAGES)
|
|
3778
|
+
throw new Error(`At most ${MAX_AGENT_ARTIFACT_IMAGES} images can be viewed in one user turn.`);
|
|
3779
|
+
turnImages.set(reference, await readImage(file));
|
|
3770
3780
|
context.metadata.turnImages ??= [];
|
|
3771
3781
|
if (!context.metadata.turnImages.some((image) => (image.id ?? image.key) === reference))
|
|
3772
3782
|
context.metadata.turnImages.push(file);
|
|
@@ -4040,12 +4050,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
4040
4050
|
const coreConnections = new Map([[configuredModel, coreConnection]]);
|
|
4041
4051
|
const coreCacheStates = new Map([[configuredModel, promptCacheState]]);
|
|
4042
4052
|
const preparePrompt = async (messages) => {
|
|
4043
|
-
messages = await resolveToolArtifacts(messages, (
|
|
4044
|
-
? createAgentArtifacts({
|
|
4045
|
-
scope: artifactScope,
|
|
4046
|
-
invoke: (operation) => invokeArtifact(operation, toolName, { toolCallId }),
|
|
4047
|
-
})
|
|
4048
|
-
: undefined);
|
|
4053
|
+
messages = await resolveToolArtifacts(messages, (id, mediaType) => readImage({ id, mediaType }));
|
|
4049
4054
|
const instructionCount = payload.instructions.length;
|
|
4050
4055
|
const prepared = addCacheControlToMessages([...payload.instructions, ...messages], (coreModel === configuredModel
|
|
4051
4056
|
? payload.cacheControlProvider
|
|
@@ -4657,6 +4662,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
4657
4662
|
async execute({ writer }) {
|
|
4658
4663
|
for await (const chunk of projectAgentCoreEvents(tracked, {
|
|
4659
4664
|
messageId,
|
|
4665
|
+
approvals: pending?.pendingStep.approvals,
|
|
4660
4666
|
tools,
|
|
4661
4667
|
messageMetadata: {
|
|
4662
4668
|
trace: invocationTrace,
|
|
@@ -8,6 +8,10 @@ export declare function projectAgentCoreEvents(turn: AsyncGenerator<AgentCoreEve
|
|
|
8
8
|
messageMetadata?: unknown;
|
|
9
9
|
/** Preserve AI SDK's declared Tool kind; raw hosts default to dynamic tools. */
|
|
10
10
|
tools?: ToolSet;
|
|
11
|
+
approvals?: ReadonlyArray<{
|
|
12
|
+
id: string;
|
|
13
|
+
call_id: string;
|
|
14
|
+
}>;
|
|
11
15
|
onError?: (error: Error) => string;
|
|
12
16
|
}): AsyncGenerator<UIMessageChunk>;
|
|
13
17
|
export declare function createAgentCoreUIStream(core: ReturnType<typeof createAgentCore>, message: AgentCoreInput, host: AgentCoreHost, options: {
|
|
@@ -15,6 +19,10 @@ export declare function createAgentCoreUIStream(core: ReturnType<typeof createAg
|
|
|
15
19
|
signal?: AbortSignal;
|
|
16
20
|
messageMetadata?: unknown;
|
|
17
21
|
tools?: ToolSet;
|
|
22
|
+
approvals?: ReadonlyArray<{
|
|
23
|
+
id: string;
|
|
24
|
+
call_id: string;
|
|
25
|
+
}>;
|
|
18
26
|
onError?: (error: Error) => string;
|
|
19
27
|
}): ReadableStream<UIMessageChunk>;
|
|
20
28
|
export declare function createAgentCoreUIResponse(stream: ReadableStream<UIMessageChunk>, init?: ResponseInit): Response;
|
|
@@ -13,6 +13,8 @@ export async function* projectAgentCoreEvents(turn, options) {
|
|
|
13
13
|
let resumed = false;
|
|
14
14
|
const blocks = new Map();
|
|
15
15
|
const calls = new Map();
|
|
16
|
+
const approvalCalls = new Map(options.approvals?.map((approval) => [approval.id, approval.call_id]));
|
|
17
|
+
const rejectedApprovals = new Set();
|
|
16
18
|
let lastModelHadToolCalls = false;
|
|
17
19
|
let outcome;
|
|
18
20
|
try {
|
|
@@ -116,6 +118,7 @@ export async function* projectAgentCoreEvents(turn, options) {
|
|
|
116
118
|
lastModelHadToolCalls = event.finish_reason === "tool_calls";
|
|
117
119
|
break;
|
|
118
120
|
case "approval_requested": {
|
|
121
|
+
approvalCalls.set(event.approval_id, event.call_id);
|
|
119
122
|
const call = calls.get(event.call_id);
|
|
120
123
|
if (!call)
|
|
121
124
|
throw new Error(`Missing approval tool call: ${event.call_id}`);
|
|
@@ -133,6 +136,11 @@ export async function* projectAgentCoreEvents(turn, options) {
|
|
|
133
136
|
break;
|
|
134
137
|
}
|
|
135
138
|
case "approval_resolved":
|
|
139
|
+
if (!event.approved) {
|
|
140
|
+
const callId = approvalCalls.get(event.approval_id);
|
|
141
|
+
if (callId)
|
|
142
|
+
rejectedApprovals.add(callId);
|
|
143
|
+
}
|
|
136
144
|
yield {
|
|
137
145
|
type: "tool-approval-response",
|
|
138
146
|
approvalId: event.approval_id,
|
|
@@ -169,14 +177,14 @@ export async function* projectAgentCoreEvents(turn, options) {
|
|
|
169
177
|
const call = calls.get(event.call_id);
|
|
170
178
|
if (!call)
|
|
171
179
|
throw new Error(`Missing UI tool call: ${event.call_id}`);
|
|
172
|
-
if (event.denied) {
|
|
180
|
+
if (event.denied && rejectedApprovals.has(event.call_id)) {
|
|
173
181
|
part = {
|
|
174
182
|
type: "tool-output-denied",
|
|
175
183
|
toolCallId: call.id,
|
|
176
184
|
toolName: call.name,
|
|
177
185
|
};
|
|
178
186
|
}
|
|
179
|
-
else if (event.is_error) {
|
|
187
|
+
else if (event.is_error || event.denied) {
|
|
180
188
|
const error = toolErrorSchema.safeParse(event.output);
|
|
181
189
|
part = {
|
|
182
190
|
type: "tool-error",
|
|
@@ -273,7 +281,10 @@ export function createAgentCoreUIStream(core, message, host, options) {
|
|
|
273
281
|
? AbortSignal.any([options.signal, cancellation.signal])
|
|
274
282
|
: cancellation.signal;
|
|
275
283
|
const turn = runAgentCore(core, message, host, signal);
|
|
276
|
-
const projected = projectAgentCoreEvents(turn,
|
|
284
|
+
const projected = projectAgentCoreEvents(turn, {
|
|
285
|
+
...options,
|
|
286
|
+
approvals: core.pendingApprovals,
|
|
287
|
+
});
|
|
277
288
|
let cancelled = false;
|
|
278
289
|
return new ReadableStream({
|
|
279
290
|
async pull(controller) {
|
|
@@ -35,6 +35,15 @@ export interface AgentCoreHost {
|
|
|
35
35
|
}
|
|
36
36
|
/** One turn per instance. All model/tool progression executes inside Rust. */
|
|
37
37
|
export declare function createAgentCore(module: WebAssembly.Module, config: AgentCoreConfig): {
|
|
38
|
+
pendingApprovals: {
|
|
39
|
+
id: string;
|
|
40
|
+
call_id: string;
|
|
41
|
+
response?: {
|
|
42
|
+
id: string;
|
|
43
|
+
approved: boolean;
|
|
44
|
+
reason?: string | undefined;
|
|
45
|
+
} | undefined;
|
|
46
|
+
}[];
|
|
38
47
|
run(message: AgentCoreInput, host: AgentCoreHost, emit: (event: AgentCoreEvent) => Promise<void>, signal: AbortSignal): Promise<{
|
|
39
48
|
output?: z.util.JSONType | undefined;
|
|
40
49
|
reason: "awaiting_approval" | "awaiting_tool_results" | "cancelled" | "completed" | "failed" | "model_limit" | "step_limit" | "stopped";
|
|
@@ -355,6 +355,7 @@ export function createAgentCore(module, config) {
|
|
|
355
355
|
throw new Error("Unsupported Agent Core ABI.");
|
|
356
356
|
const run = jspi.promising(exports.agent_run);
|
|
357
357
|
return {
|
|
358
|
+
pendingApprovals: configuration.pending_step?.approvals ?? [],
|
|
358
359
|
async run(message, host, emit, signal) {
|
|
359
360
|
const outcome = await this.runOutcome(message, host, emit, signal);
|
|
360
361
|
if (outcome.details)
|
package/dist/index.d.ts
CHANGED
|
@@ -427,22 +427,12 @@ export declare function defineAgentReference<const TSlug extends WorkerAgentSlug
|
|
|
427
427
|
export type DefineRemoteAgentInput = {
|
|
428
428
|
agentId: string;
|
|
429
429
|
description: string;
|
|
430
|
-
url?: string;
|
|
431
|
-
environment?: "preview" | "production" | "local";
|
|
432
|
-
auth?: {
|
|
433
|
-
bearerTokenEnv: string;
|
|
434
|
-
};
|
|
435
430
|
};
|
|
436
|
-
/** Invoke
|
|
431
|
+
/** Invoke an exported Agent in the current Project using the caller's authority and environment. */
|
|
437
432
|
export declare function defineRemoteAgent(input: DefineRemoteAgentInput): {
|
|
438
433
|
type: "remote";
|
|
439
434
|
agentId: string;
|
|
440
|
-
url?: string | undefined;
|
|
441
|
-
environment?: "local" | "preview" | "production" | undefined;
|
|
442
435
|
description: string;
|
|
443
|
-
auth?: {
|
|
444
|
-
bearerTokenEnv: string;
|
|
445
|
-
} | undefined;
|
|
446
436
|
};
|
|
447
437
|
/** @deprecated Enable defineAgent({ sessionTools: true }) instead. */
|
|
448
438
|
export declare function chatList(): DefinedManagedTool<"chat_list">;
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { agentToolOutputOffloadOptionsSchema, } from "@gea-ai/contract/agent-con
|
|
|
2
2
|
import { resolveModelOptions, } from "@gea-ai/contract/model-options";
|
|
3
3
|
export { aiSdk } from "./ai-sdk.js";
|
|
4
4
|
import { describeAgentContext } from "./context.js";
|
|
5
|
-
import { workerAgentReferenceSchema,
|
|
5
|
+
import { workerAgentReferenceSchema, remoteAgentDeclarationSchema, } from "@gea-ai/contract/agent-references";
|
|
6
6
|
import { agentConnectorAuthSchema, agentConnectorDefinitionSchema, agentConnectorGrantSchema, agentConnectorRuntimeSchema, agentEnvironmentVariableSchema, getAgentConnectorPrincipalType, agentPackageSnapshotSchema, agentSlugSchema, agentSkillSchema, agentToolSchema, GEA_AGENT_PACKAGE_SCHEMA_VERSION, } from "@gea-ai/contract/agent-packages";
|
|
7
7
|
import { agentChannelKeySchema, agentChannelPathSchema, agentCustomChannelDefinitionSchema, agentHttpChannelDefinitionSchema, canonicalAgentChannelJson, } from "@gea-ai/contract/agent-channels";
|
|
8
8
|
import { z } from "zod";
|
|
@@ -331,9 +331,9 @@ export function defineAgentReference(input) {
|
|
|
331
331
|
});
|
|
332
332
|
return { ...reference, slug: input.slug };
|
|
333
333
|
}
|
|
334
|
-
/** Invoke
|
|
334
|
+
/** Invoke an exported Agent in the current Project using the caller's authority and environment. */
|
|
335
335
|
export function defineRemoteAgent(input) {
|
|
336
|
-
return
|
|
336
|
+
return remoteAgentDeclarationSchema.parse({ ...input, type: "remote" });
|
|
337
337
|
}
|
|
338
338
|
/** @deprecated Enable defineAgent({ sessionTools: true }) instead. */
|
|
339
339
|
export function chatList() {
|
|
@@ -4,4 +4,10 @@ import type { AgentArtifacts, AgentToolModelOutput } from "@gea-ai/contract/stud
|
|
|
4
4
|
/** Persist only immutable references in the model context, never signed URLs or bytes. */
|
|
5
5
|
export declare function storeToolModelOutput(output: AgentToolModelOutput, artifacts: AgentArtifacts | undefined, signal?: AbortSignal): Promise<ToolResultOutput>;
|
|
6
6
|
/** Resolve scoped Artifact access for this request without mutating durable messages. */
|
|
7
|
-
export declare function resolveToolArtifacts(messages: ModelMessage[],
|
|
7
|
+
export declare function resolveToolArtifacts(messages: ModelMessage[], readImage: (id: string, mediaType: string) => Promise<{
|
|
8
|
+
mediaType: string;
|
|
9
|
+
data: {
|
|
10
|
+
type: "data";
|
|
11
|
+
data: Uint8Array;
|
|
12
|
+
};
|
|
13
|
+
}>): Promise<ModelMessage[]>;
|
|
@@ -68,7 +68,7 @@ export async function storeToolModelOutput(output, artifacts, signal) {
|
|
|
68
68
|
return { type: "content", value };
|
|
69
69
|
}
|
|
70
70
|
/** Resolve scoped Artifact access for this request without mutating durable messages. */
|
|
71
|
-
export async function resolveToolArtifacts(messages,
|
|
71
|
+
export async function resolveToolArtifacts(messages, readImage) {
|
|
72
72
|
return Promise.all(messages.map(async (message) => {
|
|
73
73
|
if (message.role !== "tool")
|
|
74
74
|
return message;
|
|
@@ -83,15 +83,11 @@ export async function resolveToolArtifacts(messages, artifacts) {
|
|
|
83
83
|
typeof item.data.reference !== "object" ||
|
|
84
84
|
!item.data.reference.geaArtifactId)
|
|
85
85
|
return item;
|
|
86
|
-
const
|
|
87
|
-
if (!scoped)
|
|
88
|
-
throw new Error("Tool Artifact references require Agent Artifacts.");
|
|
89
|
-
const { downloadUrl } = await scoped.get({
|
|
90
|
-
id: item.data.reference.geaArtifactId,
|
|
91
|
-
});
|
|
86
|
+
const image = await readImage(item.data.reference.geaArtifactId, item.mediaType);
|
|
92
87
|
return {
|
|
93
88
|
...item,
|
|
94
|
-
|
|
89
|
+
mediaType: image.mediaType,
|
|
90
|
+
data: image.data,
|
|
95
91
|
};
|
|
96
92
|
}));
|
|
97
93
|
return { ...part, output: { ...part.output, value } };
|
package/dist/tracing.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { agentSdkVersion } from "./version.js";
|
|
1
2
|
import { Result } from "better-result";
|
|
2
3
|
import { captureTraceContext } from "./trace-context.js";
|
|
3
4
|
import { OpenTelemetry } from "@ai-sdk/otel";
|
|
@@ -135,7 +136,7 @@ function initializeTracing() {
|
|
|
135
136
|
});
|
|
136
137
|
trace.setGlobalTracerProvider(provider);
|
|
137
138
|
registerTelemetry(new OpenTelemetry({
|
|
138
|
-
tracer: provider.getTracer("gea.agent-sdk",
|
|
139
|
+
tracer: provider.getTracer("gea.agent-sdk", agentSdkVersion),
|
|
139
140
|
headers: false,
|
|
140
141
|
providerMetadata: false,
|
|
141
142
|
runtimeContext: false,
|
|
@@ -167,7 +168,7 @@ export async function traceWorkerRequest(request, name, attributes, execute) {
|
|
|
167
168
|
};
|
|
168
169
|
const parent = extractTraceContext(request.headers).setValue(requestTraceScope, scope);
|
|
169
170
|
const span = currentProvider
|
|
170
|
-
.getTracer("gea.agent-sdk",
|
|
171
|
+
.getTracer("gea.agent-sdk", agentSdkVersion)
|
|
171
172
|
.startSpan(name, {
|
|
172
173
|
attributes,
|
|
173
174
|
kind: SpanKind.SERVER,
|
|
@@ -346,7 +347,9 @@ export function createAgentTracing(signal) {
|
|
|
346
347
|
async withSpan(name, execute, options) {
|
|
347
348
|
if (!available())
|
|
348
349
|
return await context.with(suppressTracing(ROOT_CONTEXT), () => execute(view()));
|
|
349
|
-
const span = trace
|
|
350
|
+
const span = trace
|
|
351
|
+
.getTracer("gea.agent-sdk", agentSdkVersion)
|
|
352
|
+
.startSpan(name, options);
|
|
350
353
|
return await context.with(trace.setSpan(context.active(), span), async () => {
|
|
351
354
|
// This callback boundary annotates outcomes without changing the protocol.
|
|
352
355
|
try {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const agentSdkVersion = "0.1.261002-alpha.0";
|
package/dist/version.js
ADDED
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.261002-alpha.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"homepage": "https://musegea.com/developers",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
"@ai-sdk/otel": "1.0.9",
|
|
116
116
|
"@ai-sdk/provider": "4.0.1",
|
|
117
117
|
"@ai-sdk/provider-utils": "5.0.2",
|
|
118
|
-
"@gea-ai/contract": "0.1.
|
|
118
|
+
"@gea-ai/contract": "0.1.261002-alpha.0",
|
|
119
119
|
"@opentelemetry/api": "1.9.1",
|
|
120
120
|
"@opentelemetry/context-async-hooks": "2.10.0",
|
|
121
121
|
"@opentelemetry/core": "2.10.0",
|
|
@@ -151,7 +151,7 @@
|
|
|
151
151
|
}
|
|
152
152
|
},
|
|
153
153
|
"scripts": {
|
|
154
|
-
"build": "node ../../scripts/build-worker-ai.mjs --check && node scripts/build-agent-core.mjs && pnpm --filter @gea-ai/contract build && rm -rf dist && tsc -p tsconfig.build.json && node ../../scripts/rewrite-esm-relative-imports.mjs dist && cp src/agent-core.wasm dist/agent-core.wasm",
|
|
154
|
+
"build": "node scripts/build-sdk-version.mjs && node ../../scripts/build-worker-ai.mjs --check && node scripts/build-agent-core.mjs && pnpm --filter @gea-ai/contract build && rm -rf dist && tsc -p tsconfig.build.json && node ../../scripts/rewrite-esm-relative-imports.mjs dist && cp src/agent-core.wasm dist/agent-core.wasm",
|
|
155
155
|
"pack:npm": "pnpm pack --pack-destination dist",
|
|
156
156
|
"publish:npm": "pnpm publish --access public --tag alpha",
|
|
157
157
|
"test": "vp test run",
|