@gea-ai/agent-sdk 0.1.260923-alpha.0 → 0.1.260923-alpha.2
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 +35 -23
- package/dist/agent-worker.js +98 -13
- package/package.json +2 -11
- package/dist/studio-client.d.ts +0 -22
- package/dist/studio-client.js +0 -97
- package/dist/studio-server.d.ts +0 -29
- package/dist/studio-server.js +0 -92
- package/dist/studio-stream-replay.d.ts +0 -4
- package/dist/studio-stream-replay.js +0 -62
|
@@ -471,31 +471,43 @@ export async function handleAgentSessionExecutionRequest(request, storage) {
|
|
|
471
471
|
}));
|
|
472
472
|
}
|
|
473
473
|
if (request.method === "POST" && url.pathname === "/v2/mutations") {
|
|
474
|
+
const startedAt = performance.now();
|
|
474
475
|
const input = agentSessionExecutionMutationSchema.parse(await readBody(request, storage));
|
|
475
476
|
const inputDigest = await digest(input);
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
477
|
+
const parsedAt = performance.now();
|
|
478
|
+
let handlerMs = 0;
|
|
479
|
+
const result = await storage.transaction(async (tx) => {
|
|
480
|
+
const handlerStartedAt = performance.now();
|
|
481
|
+
try {
|
|
482
|
+
const turn = await requireAttempt(tx, input);
|
|
483
|
+
if (input.mutationSequence === turn.nextMutationSequence - 1 &&
|
|
484
|
+
turn.lastMutation?.digest === inputDigest)
|
|
485
|
+
return { duplicate: true, revision: turn.lastMutation.revision };
|
|
486
|
+
if (input.mutationSequence !== turn.nextMutationSequence)
|
|
487
|
+
throw new SessionProtocolError("mutation_sequence_mismatch");
|
|
488
|
+
if (input.context && input.context.runId !== input.runId)
|
|
489
|
+
throw new SessionProtocolError("context_run_mismatch");
|
|
490
|
+
const previousRevision = await readRevision(tx);
|
|
491
|
+
await writeValues(tx, input);
|
|
492
|
+
const activeTurn = {
|
|
493
|
+
...turn,
|
|
494
|
+
nextMutationSequence: turn.nextMutationSequence + 1,
|
|
495
|
+
lastMutation: {
|
|
496
|
+
digest: inputDigest,
|
|
497
|
+
revision: previousRevision + 1,
|
|
498
|
+
},
|
|
499
|
+
};
|
|
500
|
+
await tx.put(key("active-turn"), activeTurn);
|
|
501
|
+
const revision = await advanceRevision(tx);
|
|
502
|
+
return { duplicate: false, revision };
|
|
503
|
+
}
|
|
504
|
+
finally {
|
|
505
|
+
handlerMs = performance.now() - handlerStartedAt;
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
const response = json(result);
|
|
509
|
+
response.headers.set("server-timing", `session-parse;dur=${(parsedAt - startedAt).toFixed(1)}, session-handler;dur=${handlerMs.toFixed(1)}, session-transaction;dur=${(performance.now() - parsedAt).toFixed(1)}`);
|
|
510
|
+
return response;
|
|
499
511
|
}
|
|
500
512
|
if (request.method === "POST" && url.pathname === "/v2/turns/pause") {
|
|
501
513
|
const input = agentSessionExecutionPauseSchema.parse(await readBody(request, storage));
|
package/dist/agent-worker.js
CHANGED
|
@@ -2604,6 +2604,7 @@ async function requestSessionJson(input) {
|
|
|
2604
2604
|
method: input.method,
|
|
2605
2605
|
})),
|
|
2606
2606
|
});
|
|
2607
|
+
input.onResponse?.(response);
|
|
2607
2608
|
if (!response.ok)
|
|
2608
2609
|
throw new Error(`AgentSession rejected ${input.path} (${response.status}).`);
|
|
2609
2610
|
return input.schema.parse(await response.json());
|
|
@@ -3025,13 +3026,19 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
3025
3026
|
}
|
|
3026
3027
|
const mutateSession = (values) => {
|
|
3027
3028
|
const queuedAt = performance.now();
|
|
3029
|
+
const mutationSpan = trace
|
|
3030
|
+
.getTracer("gea.agent-session")
|
|
3031
|
+
.startSpan("agent.session.mutation", {
|
|
3032
|
+
attributes: { "gea.agent.run.id": payload.runId },
|
|
3033
|
+
});
|
|
3028
3034
|
const result = writeTail.then(async () => {
|
|
3029
|
-
if (writeFailure)
|
|
3030
|
-
throw writeFailure;
|
|
3031
3035
|
const startedAt = performance.now();
|
|
3032
3036
|
const queueMs = startedAt - queuedAt;
|
|
3037
|
+
const sequence = mutationSequence;
|
|
3033
3038
|
let failed = true;
|
|
3034
3039
|
try {
|
|
3040
|
+
if (writeFailure)
|
|
3041
|
+
throw writeFailure;
|
|
3035
3042
|
await requestSessionJson({
|
|
3036
3043
|
body: { ...values, ...sessionExecution, mutationSequence },
|
|
3037
3044
|
chatId: payload.chatId,
|
|
@@ -3039,6 +3046,12 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
3039
3046
|
namespace: input.sessionNamespace,
|
|
3040
3047
|
path: "/v2/mutations",
|
|
3041
3048
|
schema: agentSessionExecutionMutationResultSchema,
|
|
3049
|
+
onResponse(response) {
|
|
3050
|
+
mutationSpan.setAttribute("http.response.status_code", response.status);
|
|
3051
|
+
const timing = response.headers.get("server-timing");
|
|
3052
|
+
if (timing)
|
|
3053
|
+
mutationSpan.setAttribute("gea.session.mutation.server_timing", timing);
|
|
3054
|
+
},
|
|
3042
3055
|
});
|
|
3043
3056
|
mutationSequence += 1;
|
|
3044
3057
|
failed = false;
|
|
@@ -3059,6 +3072,22 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
3059
3072
|
"gea.session.persistence.queue_ms": Math.round(persistenceTotals.queueMs),
|
|
3060
3073
|
"gea.session.persistence.request_ms": Math.round(persistenceTotals.requestMs),
|
|
3061
3074
|
});
|
|
3075
|
+
mutationSpan.setAttributes({
|
|
3076
|
+
"gea.session.mutation.sequence": sequence,
|
|
3077
|
+
"gea.session.mutation.kinds": Object.keys(values),
|
|
3078
|
+
"gea.session.mutation.event_count": values.events?.length ?? 0,
|
|
3079
|
+
"gea.session.mutation.queue_ms": Math.round(queueMs),
|
|
3080
|
+
"gea.session.mutation.request_ms": Math.round(requestMs),
|
|
3081
|
+
...(invocationTiming.lastModelFinishedAt === undefined
|
|
3082
|
+
? {}
|
|
3083
|
+
: {
|
|
3084
|
+
"gea.session.mutation.start_after_model_ms": Math.round(startedAt - invocationTiming.lastModelFinishedAt),
|
|
3085
|
+
"gea.session.mutation.end_after_model_ms": Math.round(performance.now() - invocationTiming.lastModelFinishedAt),
|
|
3086
|
+
}),
|
|
3087
|
+
});
|
|
3088
|
+
if (failed)
|
|
3089
|
+
mutationSpan.setStatus({ code: SpanStatusCode.ERROR });
|
|
3090
|
+
mutationSpan.end();
|
|
3062
3091
|
if (failed || queueMs + requestMs >= 250)
|
|
3063
3092
|
console.info("AgentSession mutation timing", {
|
|
3064
3093
|
runId: payload.runId,
|
|
@@ -3876,6 +3905,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
3876
3905
|
let contextCheckpoint;
|
|
3877
3906
|
let reusedContextCheckpoints = 0;
|
|
3878
3907
|
let modelCallsInitialized = false;
|
|
3908
|
+
const checkpointModelCalls = [];
|
|
3879
3909
|
const persistModelContext = () => {
|
|
3880
3910
|
const modelContext = {
|
|
3881
3911
|
contextSize: context.contextSize,
|
|
@@ -3887,14 +3917,19 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
3887
3917
|
// Lifecycle callbacks can checkpoint the same projection several times.
|
|
3888
3918
|
// Reuse the pending write too: an unchanged snapshot must still await its
|
|
3889
3919
|
// durable commit, including propagating a failed commit to every waiter.
|
|
3890
|
-
if (contextCheckpoint?.serialized === serialized
|
|
3920
|
+
if (contextCheckpoint?.serialized === serialized &&
|
|
3921
|
+
checkpointModelCalls.length === 0) {
|
|
3891
3922
|
persistenceSpan?.setAttribute("gea.session.persistence.context_reused", ++reusedContextCheckpoints);
|
|
3892
3923
|
return contextCheckpoint.pending;
|
|
3893
3924
|
}
|
|
3894
3925
|
const pending = sessionExecution
|
|
3895
3926
|
? mutateSession({
|
|
3896
3927
|
context: modelContext,
|
|
3897
|
-
...(
|
|
3928
|
+
...(checkpointModelCalls.length > 0
|
|
3929
|
+
? { modelCalls: checkpointModelCalls.splice(0) }
|
|
3930
|
+
: !modelCallsInitialized
|
|
3931
|
+
? { modelCalls: [] }
|
|
3932
|
+
: {}),
|
|
3898
3933
|
})
|
|
3899
3934
|
: requestSessionJson({
|
|
3900
3935
|
body: { context: modelContext },
|
|
@@ -3941,11 +3976,14 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
3941
3976
|
},
|
|
3942
3977
|
});
|
|
3943
3978
|
const modelCalls = [];
|
|
3944
|
-
const recordModelCall = async (entry) => {
|
|
3979
|
+
const recordModelCall = async (entry, checkpoint = false) => {
|
|
3945
3980
|
const call = agentModelCallSchema.parse(entry);
|
|
3946
3981
|
modelCalls.push(call);
|
|
3947
3982
|
if (sessionExecution) {
|
|
3948
|
-
|
|
3983
|
+
if (checkpoint)
|
|
3984
|
+
checkpointModelCalls.push(call);
|
|
3985
|
+
else
|
|
3986
|
+
await mutateSession({ modelCalls: [call] });
|
|
3949
3987
|
}
|
|
3950
3988
|
else {
|
|
3951
3989
|
await appendStreamEvent({ type: "data-model-call", data: call }, `model-call:${call.callId}`);
|
|
@@ -4074,7 +4112,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
4074
4112
|
failure ??= { error };
|
|
4075
4113
|
recordExecutionFailure(error);
|
|
4076
4114
|
};
|
|
4077
|
-
const recordMainCall = async (status, usage) => {
|
|
4115
|
+
const recordMainCall = async (status, usage, checkpoint = false) => {
|
|
4078
4116
|
if (!pendingMainCall)
|
|
4079
4117
|
return;
|
|
4080
4118
|
const callId = pendingMainCall;
|
|
@@ -4087,7 +4125,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
4087
4125
|
status,
|
|
4088
4126
|
usage,
|
|
4089
4127
|
occurredAt: new Date().toISOString(),
|
|
4090
|
-
});
|
|
4128
|
+
}, checkpoint);
|
|
4091
4129
|
};
|
|
4092
4130
|
let settling;
|
|
4093
4131
|
const finishContext = (end) => {
|
|
@@ -4100,6 +4138,10 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
4100
4138
|
if (end?.finishReason === "stop" && !failure)
|
|
4101
4139
|
await inboxRunner?.join();
|
|
4102
4140
|
await recordMainCall(input.request.signal.aborted ? "aborted" : "failed", null);
|
|
4141
|
+
// A failed/interrupted Core save or offload can skip the normal model
|
|
4142
|
+
// checkpoint. Persist known usage before any fallible onEnd hook.
|
|
4143
|
+
if (checkpointModelCalls.length > 0)
|
|
4144
|
+
await mutateSession({ modelCalls: checkpointModelCalls.splice(0) });
|
|
4103
4145
|
await contextRuntime.onEnd(lastStepNumber, end ?? {
|
|
4104
4146
|
totalUsage: mainUsage ?? {
|
|
4105
4147
|
inputTokens: undefined,
|
|
@@ -4150,6 +4192,8 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
4150
4192
|
})();
|
|
4151
4193
|
return settling;
|
|
4152
4194
|
};
|
|
4195
|
+
let terminalMessage;
|
|
4196
|
+
let terminalEvent;
|
|
4153
4197
|
const saveResponseMessage = async ({ responseMessage, }) => {
|
|
4154
4198
|
const saveStartedAt = performance.now();
|
|
4155
4199
|
const metadata = await contextFinished;
|
|
@@ -4172,9 +4216,19 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
4172
4216
|
...metadata,
|
|
4173
4217
|
};
|
|
4174
4218
|
const transcriptStartedAt = performance.now();
|
|
4219
|
+
const combineTerminal = sessionExecution &&
|
|
4220
|
+
!input.channelJournal &&
|
|
4221
|
+
!inboxRunner?.suspended &&
|
|
4222
|
+
!failure &&
|
|
4223
|
+
metadata.finishReason === "stop";
|
|
4175
4224
|
// Retain the generated transcript even if context settlement failed.
|
|
4176
4225
|
// A failed settlement must never produce a successful Run completion.
|
|
4177
|
-
if (
|
|
4226
|
+
if (combineTerminal) {
|
|
4227
|
+
// The managed durable stream drains independently of the HTTP consumer.
|
|
4228
|
+
// Its flush commits this transcript with finish before publishing success.
|
|
4229
|
+
terminalMessage = { ...responseMessage };
|
|
4230
|
+
}
|
|
4231
|
+
else if (sessionExecution) {
|
|
4178
4232
|
await mutateSession({ messages: [{ ...responseMessage }] });
|
|
4179
4233
|
}
|
|
4180
4234
|
else {
|
|
@@ -4188,7 +4242,17 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
4188
4242
|
const transcriptFinishedAt = performance.now();
|
|
4189
4243
|
if (failure)
|
|
4190
4244
|
throw failure.error;
|
|
4191
|
-
|
|
4245
|
+
try {
|
|
4246
|
+
await persistModelContext();
|
|
4247
|
+
}
|
|
4248
|
+
catch (error) {
|
|
4249
|
+
// Preserve the transcript on a failed final context write as before.
|
|
4250
|
+
if (terminalMessage) {
|
|
4251
|
+
await mutateSession({ messages: [{ ...terminalMessage }] });
|
|
4252
|
+
terminalMessage = undefined;
|
|
4253
|
+
}
|
|
4254
|
+
throw error;
|
|
4255
|
+
}
|
|
4192
4256
|
const contextPersistedAt = performance.now();
|
|
4193
4257
|
persistenceSpan?.setAttributes({
|
|
4194
4258
|
"gea.session.finish.context_wait_ms": Math.round(contextFinishedAt - saveStartedAt),
|
|
@@ -4530,7 +4594,9 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
4530
4594
|
const usage = coreUsage(part.usage);
|
|
4531
4595
|
mainUsage = addAgentWorkerUsage(mainUsage, usage);
|
|
4532
4596
|
invocationTiming.lastModelFinishedAt = performance.now();
|
|
4533
|
-
|
|
4597
|
+
// Core saves its validated model response before running tools or
|
|
4598
|
+
// declaring completion. Commit usage with that same checkpoint.
|
|
4599
|
+
await recordMainCall("completed", usage, true);
|
|
4534
4600
|
modelSpan?.setAttribute("gen_ai.output.messages", JSON.stringify(toModelMessages([part.message])));
|
|
4535
4601
|
if (usage.inputTokens !== undefined)
|
|
4536
4602
|
modelSpan?.setAttribute("gen_ai.usage.input_tokens", usage.inputTokens);
|
|
@@ -4654,7 +4720,7 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
4654
4720
|
mainUsage = addAgentWorkerUsage(mainUsage, step.usage);
|
|
4655
4721
|
lastStepNumber = step.stepNumber;
|
|
4656
4722
|
try {
|
|
4657
|
-
await recordMainCall(step.finishReason === "error" ? "failed" : "completed", step.usage);
|
|
4723
|
+
await recordMainCall(step.finishReason === "error" ? "failed" : "completed", step.usage, true);
|
|
4658
4724
|
await contextRuntime.offload();
|
|
4659
4725
|
await persistModelContext();
|
|
4660
4726
|
await contextRuntime.onStepEnd(step.stepNumber, {
|
|
@@ -4804,6 +4870,13 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
4804
4870
|
controller.enqueue(chunk);
|
|
4805
4871
|
continue;
|
|
4806
4872
|
}
|
|
4873
|
+
if (sessionExecution &&
|
|
4874
|
+
!input.channelJournal &&
|
|
4875
|
+
!inboxRunner?.suspended &&
|
|
4876
|
+
metadata.finishReason === "stop") {
|
|
4877
|
+
terminalEvent = chunk;
|
|
4878
|
+
continue;
|
|
4879
|
+
}
|
|
4807
4880
|
}
|
|
4808
4881
|
events.push(chunk);
|
|
4809
4882
|
}
|
|
@@ -4817,8 +4890,20 @@ async function executeAgentWorkerRequest(input, waitUntil, ownComputer, ownSessi
|
|
|
4817
4890
|
for (const event of events)
|
|
4818
4891
|
controller.enqueue(event);
|
|
4819
4892
|
},
|
|
4893
|
+
async flush(controller) {
|
|
4894
|
+
if (!terminalMessage && !terminalEvent)
|
|
4895
|
+
return;
|
|
4896
|
+
const startedAt = performance.now();
|
|
4897
|
+
await mutateSession({
|
|
4898
|
+
...(terminalMessage ? { messages: [{ ...terminalMessage }] } : {}),
|
|
4899
|
+
...(terminalEvent ? { events: [terminalEvent] } : {}),
|
|
4900
|
+
});
|
|
4901
|
+
persistenceSpan?.setAttribute("gea.session.finish.transcript_ms", Math.round(performance.now() - startedAt));
|
|
4902
|
+
if (terminalEvent)
|
|
4903
|
+
controller.enqueue(terminalEvent);
|
|
4904
|
+
},
|
|
4820
4905
|
}));
|
|
4821
|
-
if (engine) {
|
|
4906
|
+
if (engine || sessionExecution) {
|
|
4822
4907
|
const [owned, response] = durableStream.tee();
|
|
4823
4908
|
waitUntil((async () => {
|
|
4824
4909
|
const reader = owned.getReader();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gea-ai/agent-sdk",
|
|
3
|
-
"version": "0.1.260923-alpha.
|
|
3
|
+
"version": "0.1.260923-alpha.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"homepage": "https://musegea.com/developers",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -36,15 +36,6 @@
|
|
|
36
36
|
"types": "./dist/evals.d.ts",
|
|
37
37
|
"import": "./dist/evals.js"
|
|
38
38
|
},
|
|
39
|
-
"./studio-client": {
|
|
40
|
-
"types": "./dist/studio-client.d.ts",
|
|
41
|
-
"import": "./dist/studio-client.js"
|
|
42
|
-
},
|
|
43
|
-
"./studio-server": {
|
|
44
|
-
"browser": null,
|
|
45
|
-
"types": "./dist/studio-server.d.ts",
|
|
46
|
-
"import": "./dist/studio-server.js"
|
|
47
|
-
},
|
|
48
39
|
"./computer": {
|
|
49
40
|
"types": "./dist/computer.d.ts",
|
|
50
41
|
"import": "./dist/computer.js"
|
|
@@ -108,7 +99,7 @@
|
|
|
108
99
|
"@ai-sdk/otel": "1.0.9",
|
|
109
100
|
"@ai-sdk/provider": "4.0.1",
|
|
110
101
|
"@ai-sdk/provider-utils": "5.0.2",
|
|
111
|
-
"@gea-ai/contract": "0.1.260923-alpha.
|
|
102
|
+
"@gea-ai/contract": "0.1.260923-alpha.2",
|
|
112
103
|
"@opentelemetry/api": "1.9.1",
|
|
113
104
|
"@opentelemetry/context-async-hooks": "2.10.0",
|
|
114
105
|
"@opentelemetry/core": "2.10.0",
|
package/dist/studio-client.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { DefaultChatTransport, type ChatTransport, type HttpChatTransportInitOptions, type UIMessage } from "ai";
|
|
2
|
-
import type { StudioAgentApiRunContext } from "@gea-ai/contract";
|
|
3
|
-
import type { StudioAgentStreamReplay } from "@gea-ai/contract/studio-agent-api";
|
|
4
|
-
export type StudioAgentChatTransportOptions<UI_MESSAGE extends UIMessage = UIMessage> = Pick<HttpChatTransportInitOptions<UI_MESSAGE>, "headers" | "credentials" | "fetch"> & {
|
|
5
|
-
/** Same-origin application backend path, without /run. Never a GEA URL. */
|
|
6
|
-
api: string;
|
|
7
|
-
/** Restore an existing GEA Chat; the AI SDK's local chat ID is independent. */
|
|
8
|
-
chatId?: string;
|
|
9
|
-
/** Called as soon as a response identifies its Run, before reading the stream. */
|
|
10
|
-
onRun?: (run: StudioAgentApiRunContext) => void;
|
|
11
|
-
/** Replace the matching assistant baseline before AI SDK consumes a full replay. */
|
|
12
|
-
onReplay?: (replay: StudioAgentStreamReplay<UI_MESSAGE>) => void | Promise<void>;
|
|
13
|
-
};
|
|
14
|
-
/** One transport per conversation. The caller's backend keeps the Project API key. */
|
|
15
|
-
export declare class StudioAgentChatTransport<UI_MESSAGE extends UIMessage = UIMessage> extends DefaultChatTransport<UI_MESSAGE> {
|
|
16
|
-
private serverChatId;
|
|
17
|
-
private latestRun;
|
|
18
|
-
constructor(options: StudioAgentChatTransportOptions<UI_MESSAGE>);
|
|
19
|
-
get chatId(): string | undefined;
|
|
20
|
-
get lastRun(): StudioAgentApiRunContext | undefined;
|
|
21
|
-
reconnectToStream(options: Parameters<ChatTransport<UI_MESSAGE>["reconnectToStream"]>[0]): Promise<ReadableStream<import("ai").UIMessageChunk<unknown, import("ai").UIDataTypes>> | null>;
|
|
22
|
-
}
|
package/dist/studio-client.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import { AGENT_APPLICATION_CHAT_ID_HEADER, AGENT_APPLICATION_RUN_ID_HEADER, AGENT_APPLICATION_REQUEST_ID_HEADER, agentApplicationUserMessageSchema, } from "@gea-ai/contract/agent-applications";
|
|
2
|
-
import { DefaultChatTransport, } from "ai";
|
|
3
|
-
import { restoreStudioStreamReplay } from "./studio-stream-replay.js";
|
|
4
|
-
/** One transport per conversation. The caller's backend keeps the Project API key. */
|
|
5
|
-
export class StudioAgentChatTransport extends DefaultChatTransport {
|
|
6
|
-
serverChatId;
|
|
7
|
-
latestRun;
|
|
8
|
-
constructor(options) {
|
|
9
|
-
const api = options.api.replace(/\/$/u, "");
|
|
10
|
-
if (!api.startsWith("/") || api.startsWith("//") || /[\\\s?#]/u.test(api)) {
|
|
11
|
-
throw new Error("Studio transport requires an application backend path such as /api/agent.");
|
|
12
|
-
}
|
|
13
|
-
super({
|
|
14
|
-
api: `${api}/run`,
|
|
15
|
-
credentials: options.credentials ?? "same-origin",
|
|
16
|
-
headers: options.headers,
|
|
17
|
-
fetch: async (url, init) => {
|
|
18
|
-
const response = await (options.fetch ?? globalThis.fetch)(url, init);
|
|
19
|
-
const chatId = response.headers.get(AGENT_APPLICATION_CHAT_ID_HEADER);
|
|
20
|
-
const runId = response.headers.get(AGENT_APPLICATION_RUN_ID_HEADER);
|
|
21
|
-
const requestId = response.headers.get(AGENT_APPLICATION_REQUEST_ID_HEADER);
|
|
22
|
-
// Failed startup can still create a Chat. Retain it for recovery.
|
|
23
|
-
if (chatId)
|
|
24
|
-
this.serverChatId = chatId;
|
|
25
|
-
if (chatId && runId && requestId) {
|
|
26
|
-
this.latestRun = { chatId, runId, requestId };
|
|
27
|
-
options.onRun?.(this.latestRun);
|
|
28
|
-
}
|
|
29
|
-
if (init?.method === "GET" &&
|
|
30
|
-
response.ok &&
|
|
31
|
-
response.status !== 204 &&
|
|
32
|
-
options.onReplay) {
|
|
33
|
-
return restoreStudioStreamReplay(response, this.serverChatId, options.onReplay);
|
|
34
|
-
}
|
|
35
|
-
return response;
|
|
36
|
-
},
|
|
37
|
-
prepareSendMessagesRequest: ({ messages, trigger }) => {
|
|
38
|
-
if (trigger === "regenerate-message") {
|
|
39
|
-
throw new Error("Studio Agent API does not support regenerating a previous turn.");
|
|
40
|
-
}
|
|
41
|
-
const message = messages.at(-1);
|
|
42
|
-
if (!message)
|
|
43
|
-
throw new Error("Studio Agent API requires a message.");
|
|
44
|
-
if (message.role === "assistant") {
|
|
45
|
-
if (!this.serverChatId)
|
|
46
|
-
throw new Error("Tool continuation requires an existing Studio Chat.");
|
|
47
|
-
return {
|
|
48
|
-
body: {
|
|
49
|
-
chatId: this.serverChatId,
|
|
50
|
-
message: {
|
|
51
|
-
id: message.id,
|
|
52
|
-
role: "assistant",
|
|
53
|
-
parts: message.parts,
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
const userMessage = agentApplicationUserMessageSchema.parse({
|
|
59
|
-
role: message.role,
|
|
60
|
-
parts: message.parts,
|
|
61
|
-
});
|
|
62
|
-
if (userMessage.parts.some((part) => part.type === "file"))
|
|
63
|
-
return {
|
|
64
|
-
body: {
|
|
65
|
-
...(this.serverChatId ? { chatId: this.serverChatId } : {}),
|
|
66
|
-
message: userMessage,
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
const text = message.parts
|
|
70
|
-
.filter((part) => part.type === "text")
|
|
71
|
-
.map((part) => part.text)
|
|
72
|
-
.join("\n");
|
|
73
|
-
return {
|
|
74
|
-
body: {
|
|
75
|
-
...(this.serverChatId ? { chatId: this.serverChatId } : {}),
|
|
76
|
-
message: text,
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
},
|
|
80
|
-
prepareReconnectToStreamRequest: () => ({
|
|
81
|
-
api: `${api}/chats/${encodeURIComponent(this.serverChatId)}/stream`,
|
|
82
|
-
}),
|
|
83
|
-
});
|
|
84
|
-
this.serverChatId = options.chatId;
|
|
85
|
-
}
|
|
86
|
-
get chatId() {
|
|
87
|
-
return this.serverChatId;
|
|
88
|
-
}
|
|
89
|
-
get lastRun() {
|
|
90
|
-
return this.latestRun;
|
|
91
|
-
}
|
|
92
|
-
async reconnectToStream(options) {
|
|
93
|
-
if (!this.serverChatId)
|
|
94
|
-
return null;
|
|
95
|
-
return super.reconnectToStream(options);
|
|
96
|
-
}
|
|
97
|
-
}
|
package/dist/studio-server.d.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { type StudioAgentCreateUploadInput, type StudioAgentApiRunInput } from "@gea-ai/contract/studio-agent-api";
|
|
2
|
-
export type StudioAgentClientOptions = {
|
|
3
|
-
/** GEA Agent environment base URL or the copied canonical /run URL. */
|
|
4
|
-
api: string;
|
|
5
|
-
/** Project API key or current GEA user's OAuth access token. */
|
|
6
|
-
token: string;
|
|
7
|
-
fetch?: typeof globalThis.fetch;
|
|
8
|
-
};
|
|
9
|
-
/**
|
|
10
|
-
* Backend-to-GEA client. The application authenticates users and checks Chat
|
|
11
|
-
* ownership before calling this client; it does not accept browser Requests.
|
|
12
|
-
*/
|
|
13
|
-
export declare class StudioAgentClient {
|
|
14
|
-
private readonly options;
|
|
15
|
-
private readonly api;
|
|
16
|
-
private readonly token;
|
|
17
|
-
constructor(options: StudioAgentClientOptions);
|
|
18
|
-
run(input: StudioAgentApiRunInput, options?: {
|
|
19
|
-
signal?: AbortSignal;
|
|
20
|
-
}): Promise<Response>;
|
|
21
|
-
resumeStream(chatId: string, options?: {
|
|
22
|
-
signal?: AbortSignal;
|
|
23
|
-
}): Promise<Response>;
|
|
24
|
-
/** Reserve a checksum-bound direct upload; PUT the file to the returned uploadUrl. */
|
|
25
|
-
createUpload(input: StudioAgentCreateUploadInput, options?: {
|
|
26
|
-
signal?: AbortSignal;
|
|
27
|
-
}): Promise<Response>;
|
|
28
|
-
private request;
|
|
29
|
-
}
|
package/dist/studio-server.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { buildAgentApplicationPath, parseAgentApplicationPath, AGENT_APPLICATION_CHAT_ID_HEADER, AGENT_APPLICATION_RUN_ID_HEADER, AGENT_APPLICATION_REQUEST_ID_HEADER, } from "@gea-ai/contract/agent-applications";
|
|
2
|
-
import { studioAgentApiRunInputSchema, studioAgentClientRunInputSchema, studioAgentCreateUploadInputSchema, } from "@gea-ai/contract/studio-agent-api";
|
|
3
|
-
/**
|
|
4
|
-
* Backend-to-GEA client. The application authenticates users and checks Chat
|
|
5
|
-
* ownership before calling this client; it does not accept browser Requests.
|
|
6
|
-
*/
|
|
7
|
-
export class StudioAgentClient {
|
|
8
|
-
options;
|
|
9
|
-
api;
|
|
10
|
-
token;
|
|
11
|
-
constructor(options) {
|
|
12
|
-
this.options = options;
|
|
13
|
-
const url = new URL(options.api);
|
|
14
|
-
const local = url.hostname === "localhost" ||
|
|
15
|
-
url.hostname.endsWith(".localhost") ||
|
|
16
|
-
url.hostname === "127.0.0.1" ||
|
|
17
|
-
url.hostname === "[::1]";
|
|
18
|
-
if ((url.protocol !== "https:" && !(url.protocol === "http:" && local)) ||
|
|
19
|
-
url.username ||
|
|
20
|
-
url.password ||
|
|
21
|
-
url.search ||
|
|
22
|
-
url.hash) {
|
|
23
|
-
throw new Error("Studio server client requires an HTTPS Agent URL without credentials, query or fragment (HTTP is allowed on localhost).");
|
|
24
|
-
}
|
|
25
|
-
if (!options.token?.trim())
|
|
26
|
-
throw new Error("Studio server client requires a token.");
|
|
27
|
-
const route = parseAgentApplicationPath(url.pathname);
|
|
28
|
-
if (route?.agentKey &&
|
|
29
|
-
route.segments.length === 1 &&
|
|
30
|
-
route.segments[0] === "run") {
|
|
31
|
-
url.pathname = buildAgentApplicationPath(route.agentKey);
|
|
32
|
-
}
|
|
33
|
-
this.token = options.token;
|
|
34
|
-
this.api = url.href.replace(/\/$/u, "");
|
|
35
|
-
}
|
|
36
|
-
async run(input, options = {}) {
|
|
37
|
-
const body = studioAgentApiRunInputSchema.parse(input);
|
|
38
|
-
return this.request("/run", {
|
|
39
|
-
method: "POST",
|
|
40
|
-
body: JSON.stringify(body),
|
|
41
|
-
signal: options.signal,
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
async resumeStream(chatId, options = {}) {
|
|
45
|
-
const id = studioAgentClientRunInputSchema.shape.chatId
|
|
46
|
-
.unwrap()
|
|
47
|
-
.parse(chatId);
|
|
48
|
-
return this.request(`/chats/${id}/stream`, {
|
|
49
|
-
method: "GET",
|
|
50
|
-
signal: options.signal,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
/** Reserve a checksum-bound direct upload; PUT the file to the returned uploadUrl. */
|
|
54
|
-
async createUpload(input, options = {}) {
|
|
55
|
-
return this.request("/uploads", {
|
|
56
|
-
method: "POST",
|
|
57
|
-
body: JSON.stringify(studioAgentCreateUploadInputSchema.parse(input)),
|
|
58
|
-
signal: options.signal,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
async request(path, init) {
|
|
62
|
-
// This is the HTTP response boundary: never expose transport exceptions to the browser.
|
|
63
|
-
try {
|
|
64
|
-
const upstream = await (this.options.fetch ?? globalThis.fetch)(`${this.api}${path}`, {
|
|
65
|
-
...init,
|
|
66
|
-
headers: {
|
|
67
|
-
Authorization: `Bearer ${this.token}`,
|
|
68
|
-
...(init.body ? { "Content-Type": "application/json" } : {}),
|
|
69
|
-
},
|
|
70
|
-
credentials: "omit",
|
|
71
|
-
redirect: "error",
|
|
72
|
-
cache: "no-store",
|
|
73
|
-
});
|
|
74
|
-
const headers = new Headers({ "cache-control": "no-store" });
|
|
75
|
-
for (const name of [
|
|
76
|
-
"content-type",
|
|
77
|
-
"x-vercel-ai-ui-message-stream",
|
|
78
|
-
AGENT_APPLICATION_CHAT_ID_HEADER,
|
|
79
|
-
AGENT_APPLICATION_RUN_ID_HEADER,
|
|
80
|
-
AGENT_APPLICATION_REQUEST_ID_HEADER,
|
|
81
|
-
]) {
|
|
82
|
-
const value = upstream.headers.get(name);
|
|
83
|
-
if (value !== null)
|
|
84
|
-
headers.set(name, value);
|
|
85
|
-
}
|
|
86
|
-
return new Response(upstream.body, { status: upstream.status, headers });
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
return Response.json({ code: "BAD_GATEWAY", message: "Could not reach the Agent service." }, { status: 502, headers: { "cache-control": "no-store" } });
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import { type UIMessage } from "ai";
|
|
2
|
-
import { type StudioAgentStreamReplay } from "@gea-ai/contract/studio-agent-api";
|
|
3
|
-
/** Consume only the Run baseline. The remaining SSE stays streamed with backpressure. */
|
|
4
|
-
export declare function restoreStudioStreamReplay<MESSAGE extends UIMessage>(response: Response, chatId: string, onReplay: (replay: StudioAgentStreamReplay<MESSAGE>) => void | Promise<void>): Promise<Response>;
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { validateUIMessages } from "ai";
|
|
2
|
-
import { STUDIO_AGENT_REPLAY_PREFIX, studioAgentStreamReplaySchema, } from "@gea-ai/contract/studio-agent-api";
|
|
3
|
-
/** Consume only the Run baseline. The remaining SSE stays streamed with backpressure. */
|
|
4
|
-
export async function restoreStudioStreamReplay(response, chatId, onReplay) {
|
|
5
|
-
if (!response.body)
|
|
6
|
-
throw new Error("Missing Agent replay context. Refresh conversation history.");
|
|
7
|
-
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
|
|
8
|
-
// The owned replay format starts with one JSON SSE comment line. Its JSON
|
|
9
|
-
// escapes embedded CR/LF; the normal AI SDK SSE parser handles all later bytes.
|
|
10
|
-
try {
|
|
11
|
-
let prefix = "";
|
|
12
|
-
while (!prefix.includes("\n")) {
|
|
13
|
-
const next = await reader.read();
|
|
14
|
-
if (next.done)
|
|
15
|
-
throw new Error("Missing Agent replay context. Refresh conversation history.");
|
|
16
|
-
prefix += next.value;
|
|
17
|
-
}
|
|
18
|
-
const end = prefix.indexOf("\n");
|
|
19
|
-
const line = prefix.slice(0, end);
|
|
20
|
-
if (!line.startsWith(STUDIO_AGENT_REPLAY_PREFIX))
|
|
21
|
-
throw new Error("Missing Agent replay context. Refresh conversation history.");
|
|
22
|
-
const replay = studioAgentStreamReplaySchema.parse(JSON.parse(line.slice(STUDIO_AGENT_REPLAY_PREFIX.length)));
|
|
23
|
-
if (replay.chatId !== chatId)
|
|
24
|
-
throw new Error("Agent replay belongs to another Chat.");
|
|
25
|
-
// A new Run starts with no parts. This is valid streaming state, although
|
|
26
|
-
// AI SDK's validator intentionally rejects empty submitted/history messages.
|
|
27
|
-
const message = replay.message.parts.length === 0
|
|
28
|
-
? replay.message
|
|
29
|
-
: (await validateUIMessages({ messages: [replay.message] }))[0];
|
|
30
|
-
// useChat captures its streaming state after reconnectToStream resolves.
|
|
31
|
-
await onReplay({ ...replay, message });
|
|
32
|
-
const remainder = prefix.slice(end + 1);
|
|
33
|
-
const body = new ReadableStream({
|
|
34
|
-
start(controller) {
|
|
35
|
-
if (remainder)
|
|
36
|
-
controller.enqueue(remainder);
|
|
37
|
-
},
|
|
38
|
-
async pull(controller) {
|
|
39
|
-
const next = await reader.read();
|
|
40
|
-
if (next.done) {
|
|
41
|
-
reader.releaseLock();
|
|
42
|
-
controller.close();
|
|
43
|
-
}
|
|
44
|
-
else
|
|
45
|
-
controller.enqueue(next.value);
|
|
46
|
-
},
|
|
47
|
-
async cancel(reason) {
|
|
48
|
-
await reader.cancel(reason);
|
|
49
|
-
reader.releaseLock();
|
|
50
|
-
},
|
|
51
|
-
}).pipeThrough(new TextEncoderStream());
|
|
52
|
-
const headers = new Headers(response.headers);
|
|
53
|
-
headers.delete("content-length");
|
|
54
|
-
return new Response(body, { status: response.status, headers });
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
// Cleanup must not replace the original protocol/callback failure.
|
|
58
|
-
await reader.cancel(error).catch(() => { });
|
|
59
|
-
reader.releaseLock();
|
|
60
|
-
throw error;
|
|
61
|
-
}
|
|
62
|
-
}
|