@kortyx/agent 0.26.2 → 0.27.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/index.js CHANGED
@@ -61,7 +61,7 @@ var require_internal = __commonJS({
61
61
  withSafeTraceSpan: () => withSafeTraceSpan2
62
62
  });
63
63
  module2.exports = __toCommonJS2(internal_exports);
64
- var import_errors6 = require("@kortyx/core/errors");
64
+ var import_errors8 = require("@kortyx/core/errors");
65
65
  var MAX_STACK_LENGTH = 32768;
66
66
  var MAX_CAUSE_DEPTH = 4;
67
67
  var projectDetails = (value, depth, seen) => {
@@ -71,16 +71,16 @@ var require_internal = __commonJS({
71
71
  return { type: "Error", message: "Circular error cause." };
72
72
  seen.add(objectValue);
73
73
  }
74
- const name = (0, import_errors6.errorProperty)(value, "name") ?? (value instanceof Error ? value.name : void 0);
75
- const message = (0, import_errors6.errorProperty)(value, "message");
76
- const stack = (0, import_errors6.errorProperty)(value, "stack") ?? (value instanceof Error ? (() => {
74
+ const name = (0, import_errors8.errorProperty)(value, "name") ?? (value instanceof Error ? value.name : void 0);
75
+ const message = (0, import_errors8.errorProperty)(value, "message");
76
+ const stack = (0, import_errors8.errorProperty)(value, "stack") ?? (value instanceof Error ? (() => {
77
77
  try {
78
78
  return value.stack;
79
79
  } catch {
80
80
  return void 0;
81
81
  }
82
82
  })() : void 0);
83
- const cause = depth < MAX_CAUSE_DEPTH ? (0, import_errors6.errorProperty)(value, "cause") : null;
83
+ const cause = depth < MAX_CAUSE_DEPTH ? (0, import_errors8.errorProperty)(value, "cause") : null;
84
84
  return {
85
85
  type: typeof name === "string" ? name.slice(0, 256) : "Error",
86
86
  message: (typeof value === "string" ? value : typeof message === "string" ? message : "Execution reported a fault.").slice(0, 8192),
@@ -789,14 +789,14 @@ var require_dist = __commonJS({
789
789
  workflowState: args.workflowState
790
790
  }
791
791
  });
792
- var import_errors6 = require("@kortyx/core/errors");
792
+ var import_errors8 = require("@kortyx/core/errors");
793
793
  var parseWithSchema = (schema, value, label) => {
794
794
  if (!schema) return value;
795
795
  const parsed = schema.safeParse(value);
796
796
  if (parsed.success) return parsed.data;
797
797
  const parsedError = "error" in parsed && parsed.error ? parsed.error : void 0;
798
798
  const reason = typeof parsedError?.message === "string" && parsedError.message.length > 0 ? parsedError.message : "invalid payload";
799
- throw new import_errors6.ValidationError(
799
+ throw new import_errors8.ValidationError(
800
800
  label === "useReason output" ? "MODEL_OUTPUT_SCHEMA" : "SCHEMA_VALIDATION",
801
801
  `${label} validation failed: ${reason}`,
802
802
  parsedError
@@ -3950,7 +3950,7 @@ ${tryStringify2(args.response)}`
3950
3950
  return [getValue(), setValue];
3951
3951
  }
3952
3952
  var import_core7 = require("@kortyx/core");
3953
- var import_errors7 = require("@kortyx/core/errors");
3953
+ var import_errors72 = require("@kortyx/core/errors");
3954
3954
  var ParallelChildWaiting = class extends Error {
3955
3955
  name = "ParallelChildWaiting";
3956
3956
  };
@@ -3964,14 +3964,14 @@ ${tryStringify2(args.response)}`
3964
3964
  );
3965
3965
  this.results = results;
3966
3966
  this.name = "ParallelError";
3967
- this.failure = (0, import_errors7.serializeFailure)({
3967
+ this.failure = (0, import_errors72.serializeFailure)({
3968
3968
  version: 1,
3969
3969
  code: this.code,
3970
3970
  category: "workflow",
3971
3971
  message: this.message,
3972
3972
  retryable: null,
3973
3973
  results: results.map(
3974
- (result) => result.status === "fulfilled" ? { status: "fulfilled" } : { status: "rejected", failure: (0, import_errors7.serializeFailure)(result.reason) }
3974
+ (result) => result.status === "fulfilled" ? { status: "fulfilled" } : { status: "rejected", failure: (0, import_errors72.serializeFailure)(result.reason) }
3975
3975
  )
3976
3976
  });
3977
3977
  }
@@ -3979,7 +3979,7 @@ ${tryStringify2(args.response)}`
3979
3979
  code = "PARALLEL_FAILED";
3980
3980
  failure;
3981
3981
  toJSON() {
3982
- return (0, import_errors7.serializeFailure)(this);
3982
+ return (0, import_errors72.serializeFailure)(this);
3983
3983
  }
3984
3984
  };
3985
3985
  function registerWorkflowTask(id) {
@@ -4124,11 +4124,11 @@ ${tryStringify2(args.response)}`
4124
4124
  }
4125
4125
  var import_node_crypto6 = require("crypto");
4126
4126
  var import_core8 = require("@kortyx/core");
4127
- var import_errors8 = require("@kortyx/core/errors");
4128
- var WorkflowCallError = class extends import_errors8.KortyxError {
4127
+ var import_errors82 = require("@kortyx/core/errors");
4128
+ var WorkflowCallError = class extends import_errors82.KortyxError {
4129
4129
  constructor(workflow, message, cause, failure) {
4130
- const detail = (0, import_errors8.serializeFailure)(
4131
- failure ?? cause ?? new import_errors8.WorkflowContractError(message)
4130
+ const detail = (0, import_errors82.serializeFailure)(
4131
+ failure ?? cause ?? new import_errors82.WorkflowContractError(message)
4132
4132
  );
4133
4133
  super(detail.code, `Workflow '${workflow}': ${message}`, {
4134
4134
  ...detail,
@@ -4149,12 +4149,12 @@ ${tryStringify2(args.response)}`
4149
4149
  if (v === null || typeof v === "string" || typeof v === "boolean") return v;
4150
4150
  if (typeof v === "number" && Number.isFinite(v)) return v;
4151
4151
  if (typeof v !== "object" || !v || seen.has(v)) {
4152
- throw new import_errors8.WorkflowContractError(
4152
+ throw new import_errors82.WorkflowContractError(
4153
4153
  "Workflow input and output must be JSON serializable."
4154
4154
  );
4155
4155
  }
4156
4156
  if (!Array.isArray(v) && Object.getPrototypeOf(v) !== Object.prototype) {
4157
- throw new import_errors8.WorkflowContractError(
4157
+ throw new import_errors82.WorkflowContractError(
4158
4158
  "Workflow input and output must contain plain JSON objects."
4159
4159
  );
4160
4160
  }
@@ -4175,7 +4175,7 @@ ${tryStringify2(args.response)}`
4175
4175
  useWorkflow: (args) => {
4176
4176
  const workflow = workflows[args.workflow];
4177
4177
  if (!workflow || workflow.id !== args.workflow)
4178
- throw new import_errors8.WorkflowContractError(
4178
+ throw new import_errors82.WorkflowContractError(
4179
4179
  "Workflow registry keys must match definition ids."
4180
4180
  );
4181
4181
  return useWorkflow({ ...args, workflow });
@@ -4201,23 +4201,23 @@ ${tryStringify2(args.response)}`
4201
4201
  (0, import_core8.throwIfExecutionAborted)(ctx.node.abortSignal);
4202
4202
  const service = ctx.node.callWorkflow;
4203
4203
  if (!service)
4204
- throw new import_errors8.WorkflowContractError(
4204
+ throw new import_errors82.WorkflowContractError(
4205
4205
  "useWorkflow requires an agent workflow registry."
4206
4206
  );
4207
4207
  if (!args.id.trim())
4208
- throw new import_errors8.WorkflowContractError(
4208
+ throw new import_errors82.WorkflowContractError(
4209
4209
  "useWorkflow requires a stable, nonempty id."
4210
4210
  );
4211
4211
  if (ctx.workflowCallActive && !task.group)
4212
- throw new import_errors8.WorkflowContractError(
4212
+ throw new import_errors82.WorkflowContractError(
4213
4213
  "Concurrent workflow calls require parallel([...]); otherwise await each call."
4214
4214
  );
4215
4215
  if (ctx.workflowCallIds.has(args.id))
4216
- throw new import_errors8.WorkflowContractError(
4216
+ throw new import_errors82.WorkflowContractError(
4217
4217
  `Duplicate workflow call id '${args.id}' in one node activation.`
4218
4218
  );
4219
4219
  if (ctx.workflowCallIds.size >= 64)
4220
- throw new import_errors8.WorkflowContractError(
4220
+ throw new import_errors82.WorkflowContractError(
4221
4221
  "A node may call at most 64 child workflows per activation."
4222
4222
  );
4223
4223
  ctx.workflowCallIds.add(args.id);
@@ -4298,7 +4298,7 @@ ${tryStringify2(args.response)}`
4298
4298
  workflow,
4299
4299
  record.error ?? "child failed",
4300
4300
  void 0,
4301
- record.failure ?? (0, import_errors8.serializeFailure)(void 0)
4301
+ record.failure ?? (0, import_errors82.serializeFailure)(void 0)
4302
4302
  );
4303
4303
  if (task.group && record.status === "interrupted" && record.interrupts.at(-1)?.response === void 0)
4304
4304
  throw new ParallelChildWaiting();
@@ -4334,7 +4334,7 @@ ${tryStringify2(args.response)}`
4334
4334
  throw error;
4335
4335
  }
4336
4336
  record.status = "failed";
4337
- record.failure = (0, import_errors8.serializeFailure)(error);
4337
+ record.failure = (0, import_errors82.serializeFailure)(error);
4338
4338
  record.error = record.failure.message;
4339
4339
  report("failed", { error: "Child workflow failed" });
4340
4340
  throw new WorkflowCallError(
@@ -4395,6 +4395,7 @@ ${tryStringify2(args.response)}`
4395
4395
  // src/index.ts
4396
4396
  var index_exports = {};
4397
4397
  __export(index_exports, {
4398
+ ChatLifecycleHookError: () => ChatLifecycleHookError,
4398
4399
  ExecutionRequestError: () => ExecutionRequestError,
4399
4400
  createAgent: () => createAgent,
4400
4401
  createChatRouteHandler: () => createChatRouteHandler,
@@ -4417,183 +4418,29 @@ __export(index_exports, {
4417
4418
  module.exports = __toCommonJS(index_exports);
4418
4419
 
4419
4420
  // src/adapters/http.ts
4421
+ var import_errors5 = require("@kortyx/core/errors");
4422
+ var import_stream2 = require("@kortyx/stream");
4423
+ var import_zod2 = require("zod");
4424
+
4425
+ // src/chat/lifecycle.ts
4420
4426
  var import_errors = require("@kortyx/core/errors");
4421
- var import_stream = require("@kortyx/stream");
4422
- var import_zod = require("zod");
4423
- var chatMessageSchema = import_zod.z.object({
4424
- role: import_zod.z.enum(["user", "assistant", "system"]),
4425
- content: import_zod.z.string(),
4426
- metadata: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
4427
- id: import_zod.z.string().optional(),
4428
- timestamp: import_zod.z.number().refine(Number.isFinite).optional()
4429
- }).strict();
4430
- var chatRequestBodySchema = import_zod.z.looseObject({
4431
- sessionId: import_zod.z.string().optional(),
4432
- workflowId: import_zod.z.string().optional(),
4433
- stream: import_zod.z.boolean().optional(),
4434
- context: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
4435
- messages: import_zod.z.array(chatMessageSchema)
4436
- });
4437
- var checkpointRequestBodySchema = import_zod.z.discriminatedUnion("action", [
4438
- import_zod.z.object({
4439
- action: import_zod.z.literal("list"),
4440
- sessionId: import_zod.z.string().min(1)
4441
- }).strict(),
4442
- import_zod.z.object({
4443
- action: import_zod.z.literal("get"),
4444
- checkpointId: import_zod.z.string().min(1)
4445
- }).strict(),
4446
- import_zod.z.object({
4447
- action: import_zod.z.literal("rollback"),
4448
- checkpointId: import_zod.z.string().min(1)
4449
- }).strict(),
4450
- import_zod.z.object({
4451
- action: import_zod.z.literal("fork"),
4452
- checkpointId: import_zod.z.string().min(1),
4453
- newSessionId: import_zod.z.string().min(1).optional()
4454
- }).strict()
4455
- ]);
4456
- async function readRequestJson(request) {
4457
- try {
4458
- const value = await request.json();
4459
- if (!value || typeof value !== "object" || Array.isArray(value))
4460
- throw new Error("Expected a JSON object.");
4461
- return value;
4462
- } catch (cause) {
4463
- throw new import_errors.KortyxError(
4464
- "INVALID_REQUEST",
4465
- "Request body is not valid JSON.",
4427
+ var ChatLifecycleHookError = class extends import_errors.KortyxError {
4428
+ phase;
4429
+ constructor(phase, cause) {
4430
+ super(
4431
+ "CHAT_LIFECYCLE_HOOK_FAILED",
4432
+ `Chat lifecycle hook failed: ${phase}`,
4466
4433
  {
4467
- category: "request",
4468
- retryable: false,
4469
- safeMessage: "The request body must contain a valid JSON object.",
4434
+ category: "persistence",
4435
+ retryable: null,
4436
+ safeMessage: "Chat state could not be saved.",
4470
4437
  cause
4471
4438
  }
4472
4439
  );
4440
+ this.name = "ChatLifecycleHookError";
4441
+ this.phase = phase;
4473
4442
  }
4474
- }
4475
- function createFailureResponse(error, status) {
4476
- const failure = (0, import_errors.serializeFailure)(error);
4477
- return Response.json(
4478
- { error: failure.message, failure },
4479
- { status: status ?? (0, import_errors.failureHttpStatus)(failure) }
4480
- );
4481
- }
4482
- function parseChatRequestBody(value) {
4483
- const parsed = chatRequestBodySchema.safeParse(value);
4484
- if (!parsed.success) {
4485
- const [firstIssue] = parsed.error.issues;
4486
- throw new import_errors.KortyxError("INVALID_REQUEST", firstIssue.message, {
4487
- category: "request",
4488
- retryable: false,
4489
- safeMessage: "The request body is invalid."
4490
- });
4491
- }
4492
- const sessionId = parsed.data.sessionId?.trim();
4493
- const workflowId = parsed.data.workflowId?.trim();
4494
- return {
4495
- ...sessionId ? { sessionId } : {},
4496
- ...workflowId ? { workflowId } : {},
4497
- ...typeof parsed.data.stream === "boolean" ? { stream: parsed.data.stream } : {},
4498
- ...parsed.data.context ? { context: parsed.data.context } : {},
4499
- messages: parsed.data.messages
4500
- };
4501
- }
4502
- function parseCheckpointRequestBody(value) {
4503
- const parsed = checkpointRequestBodySchema.safeParse(value);
4504
- if (parsed.success) return parsed.data;
4505
- const [firstIssue] = parsed.error.issues;
4506
- throw new import_errors.KortyxError("INVALID_REQUEST", firstIssue.message, {
4507
- category: "request",
4508
- retryable: false,
4509
- safeMessage: "The request body is invalid."
4510
- });
4511
- }
4512
- async function handleChatRequestBody(args) {
4513
- const { agent, body } = args;
4514
- const stream = await agent.streamChat(body.messages, {
4515
- ...args.onExecution ? { onExecution: args.onExecution } : {},
4516
- ...args.abortSignal ? { abortSignal: args.abortSignal } : {},
4517
- sessionId: body.sessionId,
4518
- workflowId: body.workflowId,
4519
- context: body.context
4520
- });
4521
- if (body.stream === false) {
4522
- const buffered = await (0, import_stream.collectBufferedStream)(stream);
4523
- return new Response(JSON.stringify(buffered), {
4524
- headers: {
4525
- "content-type": "application/json"
4526
- }
4527
- });
4528
- }
4529
- return (0, import_stream.toSSE)(stream);
4530
- }
4531
- function createChatRouteHandler(args) {
4532
- const { agent, errorStatus } = args;
4533
- return async function POST(request) {
4534
- try {
4535
- const body = parseChatRequestBody(await readRequestJson(request));
4536
- return await handleChatRequestBody({
4537
- agent,
4538
- body,
4539
- abortSignal: request.signal,
4540
- onExecution: args.onExecution
4541
- });
4542
- } catch (error) {
4543
- return createFailureResponse(error, errorStatus);
4544
- }
4545
- };
4546
- }
4547
- async function handleCheckpointRequestBody(args) {
4548
- const { agent, body } = args;
4549
- const result = body.action === "list" ? await agent.listCheckpoints(body.sessionId) : body.action === "get" ? await agent.getCheckpoint(body.checkpointId) : body.action === "rollback" ? await agent.rollbackTo(body.checkpointId) : await agent.fork(body.checkpointId, {
4550
- ...body.newSessionId ? { newSessionId: body.newSessionId } : {}
4551
- });
4552
- return new Response(JSON.stringify(result), {
4553
- headers: {
4554
- "content-type": "application/json"
4555
- }
4556
- });
4557
- }
4558
- function createCheckpointRouteHandler(args) {
4559
- const { agent, errorStatus } = args;
4560
- return async function POST(request) {
4561
- try {
4562
- const body = parseCheckpointRequestBody(await readRequestJson(request));
4563
- return await handleCheckpointRequestBody({ agent, body });
4564
- } catch (error) {
4565
- return createFailureResponse(error, errorStatus);
4566
- }
4567
- };
4568
- }
4569
-
4570
- // src/adapters/http-client.ts
4571
- var import_browser = require("@kortyx/stream/browser");
4572
- async function* streamChatFromRoute(args) {
4573
- yield* (0, import_browser.streamFromRoute)({
4574
- endpoint: args.endpoint,
4575
- ...args.fetchImpl ? { fetchImpl: args.fetchImpl } : {},
4576
- ...args.headers ? { headers: args.headers } : {},
4577
- body: {
4578
- sessionId: args.sessionId,
4579
- workflowId: args.workflowId,
4580
- messages: args.messages
4581
- }
4582
- });
4583
- }
4584
-
4585
- // src/chat/create-agent.ts
4586
- var import_node_path = require("path");
4587
- var import_core5 = require("@kortyx/core");
4588
- var import_providers = require("@kortyx/providers");
4589
- var import_runtime5 = require("@kortyx/runtime");
4590
- var import_zod5 = require("zod");
4591
-
4592
- // src/execution/execute.ts
4593
- var import_core4 = require("@kortyx/core");
4594
- var import_errors5 = require("@kortyx/core/errors");
4595
- var import_runtime3 = require("@kortyx/runtime");
4596
- var import_zod3 = require("zod");
4443
+ };
4597
4444
 
4598
4445
  // src/interrupt/resume-handler.ts
4599
4446
  var import_core3 = require("@kortyx/core");
@@ -4619,11 +4466,12 @@ var import_core2 = require("@kortyx/core");
4619
4466
  var import_errors4 = require("@kortyx/core/errors");
4620
4467
  var import_internal = __toESM(require_internal());
4621
4468
  var import_runtime = require("@kortyx/runtime");
4469
+ var import_stream = require("@kortyx/stream");
4622
4470
  var import_langgraph = require("@langchain/langgraph");
4623
4471
 
4624
4472
  // src/execution/contracts.ts
4625
4473
  var import_errors3 = require("@kortyx/core/errors");
4626
- var import_zod2 = require("zod");
4474
+ var import_zod = require("zod");
4627
4475
  async function resolveExecutionWorkflow(select, reference) {
4628
4476
  const id = typeof reference === "string" ? reference : reference.id;
4629
4477
  let workflow;
@@ -4655,12 +4503,12 @@ async function resolveExecutionWorkflow(select, reference) {
4655
4503
  }
4656
4504
  function parseExecutionInput(workflow, input) {
4657
4505
  try {
4658
- import_zod2.z.json().parse(input);
4506
+ import_zod.z.json().parse(input);
4659
4507
  const parsed = workflow.inputSchema ? workflow.inputSchema.parse(input) : input;
4660
- import_zod2.z.json().parse(parsed);
4508
+ import_zod.z.json().parse(parsed);
4661
4509
  return parsed;
4662
4510
  } catch (error) {
4663
- if (!(error instanceof import_zod2.z.ZodError)) throw error;
4511
+ if (!(error instanceof import_zod.z.ZodError)) throw error;
4664
4512
  throw new ExecutionRequestError("INVALID_INPUT", error.message, error);
4665
4513
  }
4666
4514
  }
@@ -4678,9 +4526,9 @@ async function validateExecutionOutput(state, select) {
4678
4526
  for (const schema of /* @__PURE__ */ new Set([terminal.outputSchema, root.outputSchema])) {
4679
4527
  if (schema) data = schema.parse(data);
4680
4528
  }
4681
- if (terminal.outputSchema || root.outputSchema) import_zod2.z.json().parse(data);
4529
+ if (terminal.outputSchema || root.outputSchema) import_zod.z.json().parse(data);
4682
4530
  } catch (error) {
4683
- if (!(error instanceof import_zod2.z.ZodError)) throw error;
4531
+ if (!(error instanceof import_zod.z.ZodError)) throw error;
4684
4532
  const failure = new import_errors3.ValidationError(
4685
4533
  "INVALID_OUTPUT",
4686
4534
  `Workflow output validation failed: ${error.message}`,
@@ -5091,6 +4939,9 @@ async function orchestrateGraphStream({
5091
4939
  abortSignal: requestSignal,
5092
4940
  executionSignal,
5093
4941
  onExecution,
4942
+ clientTurnId,
4943
+ continueOnDisconnect = false,
4944
+ onResponseFinalized,
5094
4945
  emitOutput = true,
5095
4946
  onOutcome,
5096
4947
  selectWorkflow,
@@ -5099,12 +4950,34 @@ async function orchestrateGraphStream({
5099
4950
  executionLeaseRelease
5100
4951
  }) {
5101
4952
  const out = new import_node_stream.PassThrough({ objectMode: true });
4953
+ const visibleMessage = emitOutput && clientTurnId && onResponseFinalized && initialConfig.responseCompleted !== true ? (0, import_stream.createFinalizedChatMessageAccumulator)(clientTurnId) : void 0;
4954
+ let responseFinalized = false;
4955
+ const finalizeVisibleResponse = async (status) => {
4956
+ if (!visibleMessage || !onResponseFinalized || !clientTurnId || !sessionId || responseFinalized)
4957
+ return;
4958
+ responseFinalized = true;
4959
+ try {
4960
+ await onResponseFinalized({
4961
+ sessionId,
4962
+ runId,
4963
+ clientTurnId,
4964
+ status,
4965
+ message: visibleMessage.message(),
4966
+ ...sessionCheckpointId ? { checkpointId: sessionCheckpointId } : {}
4967
+ });
4968
+ } catch (error) {
4969
+ console.error("[chat:onResponseFinalized]", (0, import_errors4.serializeFailure)(error));
4970
+ }
4971
+ };
5102
4972
  const write = (chunk) => {
5103
4973
  if (isRecord3(chunk) && chunk.type === "done" && isGraphState(chunk.data)) {
5104
4974
  const { __kortyxParallelGraph: _journal, ...runtime } = chunk.data.runtime;
5105
4975
  chunk = { ...chunk, data: { ...chunk.data, runtime } };
5106
4976
  }
5107
- if (emitOutput && !response.closed && !out.destroyed) out.write(chunk);
4977
+ if (emitOutput && !response.closed) {
4978
+ visibleMessage?.apply(chunk);
4979
+ if (!out.destroyed) out.write(chunk);
4980
+ }
5108
4981
  };
5109
4982
  let outcomeState = state;
5110
4983
  let outcomeError;
@@ -5119,7 +4992,7 @@ async function orchestrateGraphStream({
5119
4992
  const response = createResponseLifecycle({
5120
4993
  enabled: emitOutput,
5121
4994
  restored: initialConfig.responseCompleted === true,
5122
- requestSignal,
4995
+ requestSignal: continueOnDisconnect ? void 0 : requestSignal,
5123
4996
  executionSignal: executionSignal ? AbortSignal.any([executionSignal, leaseAbort.signal]) : leaseAbort.signal,
5124
4997
  onClosed: () => {
5125
4998
  out.write({ type: "done" });
@@ -5183,11 +5056,12 @@ async function orchestrateGraphStream({
5183
5056
  },
5184
5057
  flush: true
5185
5058
  });
5059
+ await finalizeVisibleResponse("completed");
5186
5060
  }
5187
5061
  });
5188
5062
  const abortSignal = response.signal;
5189
5063
  out.on("close", () => {
5190
- if (!finished) response.disconnect();
5064
+ if (!finished && !continueOnDisconnect) response.disconnect();
5191
5065
  });
5192
5066
  let structuredSeq = 0;
5193
5067
  const debugEnabled = Boolean(config?.features?.tracing);
@@ -5940,6 +5814,9 @@ async function orchestrateGraphStream({
5940
5814
  );
5941
5815
  }
5942
5816
  }
5817
+ await finalizeVisibleResponse(
5818
+ outcomeError ? "failed" : shouldKeepFrameworkState ? "interrupted" : "completed"
5819
+ );
5943
5820
  write({ type: "done", data: workflowFinalState });
5944
5821
  out.end();
5945
5822
  return;
@@ -6017,6 +5894,9 @@ async function orchestrateGraphStream({
6017
5894
  });
6018
5895
  finished = true;
6019
5896
  runTraceSpan.end?.(runTraceEndArgs());
5897
+ await finalizeVisibleResponse(
5898
+ outcomeError ? "failed" : pendingRecordToken || naturalFinalState.awaitingHumanInput ? "interrupted" : "completed"
5899
+ );
6020
5900
  write({ type: "done", data: naturalFinalState });
6021
5901
  out.end();
6022
5902
  return;
@@ -6077,6 +5957,7 @@ async function orchestrateGraphStream({
6077
5957
  fallbackRunSpan?.setAttributes?.({ "kortyx.run.cancelled": true });
6078
5958
  fallbackRunSpan?.end?.();
6079
5959
  write({ type: "cancelled", runId, reason: "Execution cancelled." });
5960
+ await finalizeVisibleResponse("cancelled");
6080
5961
  write({ type: "done" });
6081
5962
  finished = true;
6082
5963
  out.end();
@@ -6223,6 +6104,9 @@ async function orchestrateGraphStream({
6223
6104
  console.error("[execution:releaseLease]", (0, import_errors4.serializeFailure)(error));
6224
6105
  }
6225
6106
  response.dispose();
6107
+ await finalizeVisibleResponse(
6108
+ outcomeError ? (0, import_core2.isExecutionCancelled)(outcomeError) ? "cancelled" : "failed" : pendingRecordToken || outcomeState.awaitingHumanInput ? "interrupted" : "completed"
6109
+ );
6226
6110
  onOutcome?.({
6227
6111
  state: outcomeState,
6228
6112
  pending: pendingRecordToken ? activePendingRequests.get(pendingRecordToken) : void 0,
@@ -6272,6 +6156,9 @@ async function tryPrepareResumeStream({
6272
6156
  abortSignal,
6273
6157
  executionSignal,
6274
6158
  onExecution,
6159
+ clientTurnId,
6160
+ continueOnDisconnect,
6161
+ onResponseFinalized,
6275
6162
  lastMessage,
6276
6163
  meta: suppliedMeta,
6277
6164
  emitOutput,
@@ -6490,6 +6377,9 @@ async function tryPrepareResumeStream({
6490
6377
  executionSignal: continuedSignal ? AbortSignal.any([continuedSignal, leaseAbort.signal]) : leaseAbort.signal,
6491
6378
  executionLeaseRelease: releaseLease,
6492
6379
  onExecution,
6380
+ clientTurnId,
6381
+ continueOnDisconnect,
6382
+ onResponseFinalized,
6493
6383
  emitOutput,
6494
6384
  onOutcome,
6495
6385
  sessionId,
@@ -6526,7 +6416,316 @@ async function tryPrepareResumeStream({
6526
6416
  }
6527
6417
  }
6528
6418
 
6419
+ // src/adapters/http.ts
6420
+ var reportLifecycleError = async (callback, event) => {
6421
+ if (!callback) {
6422
+ console.error("[chat:lifecycle]", event.error);
6423
+ return;
6424
+ }
6425
+ try {
6426
+ await callback(event);
6427
+ } catch (error) {
6428
+ console.error("[chat:onLifecycleError]", (0, import_errors5.serializeFailure)(error));
6429
+ }
6430
+ };
6431
+ var chatMessageSchema = import_zod2.z.object({
6432
+ role: import_zod2.z.enum(["user", "assistant", "system"]),
6433
+ content: import_zod2.z.string(),
6434
+ metadata: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional(),
6435
+ id: import_zod2.z.string().optional(),
6436
+ timestamp: import_zod2.z.number().refine(Number.isFinite).optional()
6437
+ }).strict();
6438
+ var chatRequestBodySchema = import_zod2.z.looseObject({
6439
+ sessionId: import_zod2.z.string().optional(),
6440
+ clientTurnId: import_zod2.z.string().max(128).optional(),
6441
+ workflowId: import_zod2.z.string().optional(),
6442
+ stream: import_zod2.z.boolean().optional(),
6443
+ context: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.unknown()).optional(),
6444
+ messages: import_zod2.z.array(chatMessageSchema)
6445
+ });
6446
+ var checkpointRequestBodySchema = import_zod2.z.discriminatedUnion("action", [
6447
+ import_zod2.z.object({
6448
+ action: import_zod2.z.literal("list"),
6449
+ sessionId: import_zod2.z.string().min(1)
6450
+ }).strict(),
6451
+ import_zod2.z.object({
6452
+ action: import_zod2.z.literal("get"),
6453
+ checkpointId: import_zod2.z.string().min(1)
6454
+ }).strict(),
6455
+ import_zod2.z.object({
6456
+ action: import_zod2.z.literal("rollback"),
6457
+ checkpointId: import_zod2.z.string().min(1)
6458
+ }).strict(),
6459
+ import_zod2.z.object({
6460
+ action: import_zod2.z.literal("fork"),
6461
+ checkpointId: import_zod2.z.string().min(1),
6462
+ newSessionId: import_zod2.z.string().min(1).optional()
6463
+ }).strict()
6464
+ ]);
6465
+ async function readRequestJson(request) {
6466
+ try {
6467
+ const value = await request.json();
6468
+ if (!value || typeof value !== "object" || Array.isArray(value))
6469
+ throw new Error("Expected a JSON object.");
6470
+ return value;
6471
+ } catch (cause) {
6472
+ throw new import_errors5.KortyxError(
6473
+ "INVALID_REQUEST",
6474
+ "Request body is not valid JSON.",
6475
+ {
6476
+ category: "request",
6477
+ retryable: false,
6478
+ safeMessage: "The request body must contain a valid JSON object.",
6479
+ cause
6480
+ }
6481
+ );
6482
+ }
6483
+ }
6484
+ function createFailureResponse(error, status) {
6485
+ const failure = (0, import_errors5.serializeFailure)(error);
6486
+ return Response.json(
6487
+ { error: failure.message, failure },
6488
+ { status: status ?? (0, import_errors5.failureHttpStatus)(failure) }
6489
+ );
6490
+ }
6491
+ function parseChatRequestBody(value) {
6492
+ const parsed = chatRequestBodySchema.safeParse(value);
6493
+ if (!parsed.success) {
6494
+ const [firstIssue] = parsed.error.issues;
6495
+ throw new import_errors5.KortyxError("INVALID_REQUEST", firstIssue.message, {
6496
+ category: "request",
6497
+ retryable: false,
6498
+ safeMessage: "The request body is invalid."
6499
+ });
6500
+ }
6501
+ const sessionId = parsed.data.sessionId?.trim();
6502
+ const workflowId = parsed.data.workflowId?.trim();
6503
+ const clientTurnId = parsed.data.clientTurnId?.trim();
6504
+ return {
6505
+ ...sessionId ? { sessionId } : {},
6506
+ ...workflowId ? { workflowId } : {},
6507
+ ...clientTurnId ? { clientTurnId } : {},
6508
+ ...typeof parsed.data.stream === "boolean" ? { stream: parsed.data.stream } : {},
6509
+ ...parsed.data.context ? { context: parsed.data.context } : {},
6510
+ messages: parsed.data.messages
6511
+ };
6512
+ }
6513
+ function parseCheckpointRequestBody(value) {
6514
+ const parsed = checkpointRequestBodySchema.safeParse(value);
6515
+ if (parsed.success) return parsed.data;
6516
+ const [firstIssue] = parsed.error.issues;
6517
+ throw new import_errors5.KortyxError("INVALID_REQUEST", firstIssue.message, {
6518
+ category: "request",
6519
+ retryable: false,
6520
+ safeMessage: "The request body is invalid."
6521
+ });
6522
+ }
6523
+ async function handleChatRequestBody(args) {
6524
+ const { agent, body } = args;
6525
+ if (args.onResponseFinalized && (!body.sessionId || !body.clientTurnId)) {
6526
+ throw new import_errors5.KortyxError(
6527
+ "INVALID_REQUEST",
6528
+ "sessionId and clientTurnId are required with onResponseFinalized.",
6529
+ {
6530
+ category: "request",
6531
+ retryable: false,
6532
+ safeMessage: "This chat request needs a session and turn ID."
6533
+ }
6534
+ );
6535
+ }
6536
+ const stream = await agent.streamChat(body.messages, {
6537
+ ...args.onExecution ? { onExecution: args.onExecution } : {},
6538
+ ...body.clientTurnId ? { clientTurnId: body.clientTurnId } : {},
6539
+ ...args.continueOnDisconnect ? { continueOnDisconnect: true } : {},
6540
+ ...args.onResponseFinalized ? { onResponseFinalized: args.onResponseFinalized } : {},
6541
+ ...args.abortSignal && !args.continueOnDisconnect ? { abortSignal: args.abortSignal } : {},
6542
+ sessionId: body.sessionId,
6543
+ workflowId: body.workflowId,
6544
+ context: body.context
6545
+ });
6546
+ if (body.stream === false) {
6547
+ const buffered = await (0, import_stream2.collectBufferedStream)(stream);
6548
+ return new Response(JSON.stringify(buffered), {
6549
+ headers: {
6550
+ "content-type": "application/json"
6551
+ }
6552
+ });
6553
+ }
6554
+ return (0, import_stream2.toSSE)(stream);
6555
+ }
6556
+ function createChatRouteHandler(args) {
6557
+ const { agent, errorStatus } = args;
6558
+ if (args.disconnect === "continue" && !args.onExecution) {
6559
+ throw new Error(
6560
+ 'disconnect: "continue" requires onExecution to retain host lifetime.'
6561
+ );
6562
+ }
6563
+ return async function POST(request) {
6564
+ try {
6565
+ const body = parseChatRequestBody(await readRequestJson(request));
6566
+ if (args.onTurnAccepted || args.onResponseFinalized) {
6567
+ if (!body.sessionId || !body.clientTurnId) {
6568
+ throw new import_errors5.KortyxError(
6569
+ "INVALID_REQUEST",
6570
+ "sessionId and clientTurnId are required with chat lifecycle hooks.",
6571
+ {
6572
+ category: "request",
6573
+ retryable: false,
6574
+ safeMessage: "This chat request needs a session and turn ID."
6575
+ }
6576
+ );
6577
+ }
6578
+ const userMessage = body.messages.at(-1);
6579
+ if (!userMessage || userMessage.role !== "user") {
6580
+ throw new import_errors5.KortyxError(
6581
+ "INVALID_REQUEST",
6582
+ "The last chat message must be from the user.",
6583
+ {
6584
+ category: "request",
6585
+ retryable: false,
6586
+ safeMessage: "The chat request needs a user message."
6587
+ }
6588
+ );
6589
+ }
6590
+ if (args.onTurnAccepted) {
6591
+ try {
6592
+ await args.onTurnAccepted({
6593
+ request,
6594
+ sessionId: body.sessionId,
6595
+ clientTurnId: body.clientTurnId,
6596
+ userMessage: { ...userMessage, role: "user" },
6597
+ kind: parseResumeMeta(userMessage) ? "interrupt-response" : "prompt"
6598
+ });
6599
+ } catch (cause) {
6600
+ const error = new ChatLifecycleHookError("turn-accepted", cause);
6601
+ await reportLifecycleError(args.onLifecycleError, {
6602
+ phase: "turn-accepted",
6603
+ clientTurnId: body.clientTurnId,
6604
+ error
6605
+ });
6606
+ throw error;
6607
+ }
6608
+ }
6609
+ }
6610
+ return await handleChatRequestBody({
6611
+ agent,
6612
+ body,
6613
+ abortSignal: request.signal,
6614
+ onExecution: args.onExecution,
6615
+ continueOnDisconnect: args.disconnect === "continue",
6616
+ ...args.onResponseFinalized ? {
6617
+ onResponseFinalized: async (event) => {
6618
+ try {
6619
+ await args.onResponseFinalized?.({ ...event, request });
6620
+ } catch (cause) {
6621
+ await reportLifecycleError(args.onLifecycleError, {
6622
+ phase: "response-finalized",
6623
+ clientTurnId: event.clientTurnId,
6624
+ error: new ChatLifecycleHookError(
6625
+ "response-finalized",
6626
+ cause
6627
+ )
6628
+ });
6629
+ }
6630
+ }
6631
+ } : {}
6632
+ });
6633
+ } catch (error) {
6634
+ return createFailureResponse(error, errorStatus);
6635
+ }
6636
+ };
6637
+ }
6638
+ async function handleCheckpointRequestBody(args) {
6639
+ const { agent, body } = args;
6640
+ const result = body.action === "list" ? await agent.listCheckpoints(body.sessionId) : body.action === "get" ? await agent.getCheckpoint(body.checkpointId) : body.action === "rollback" ? await agent.rollbackTo(body.checkpointId) : await agent.fork(body.checkpointId, {
6641
+ ...body.newSessionId ? { newSessionId: body.newSessionId } : {}
6642
+ });
6643
+ if (args.request && body.action === "fork" && args.onForked) {
6644
+ const fork = result;
6645
+ try {
6646
+ await args.onForked({
6647
+ request: args.request,
6648
+ sourceSessionId: fork.parentSessionId,
6649
+ newSessionId: fork.sessionId,
6650
+ sourceCheckpointId: fork.forkedFrom,
6651
+ newCheckpointId: fork.checkpoint.id
6652
+ });
6653
+ } catch (cause) {
6654
+ await reportLifecycleError(args.onLifecycleError, {
6655
+ phase: "forked",
6656
+ error: new ChatLifecycleHookError("forked", cause)
6657
+ });
6658
+ }
6659
+ }
6660
+ if (args.request && body.action === "rollback" && args.onRolledBack) {
6661
+ const rollback = result;
6662
+ try {
6663
+ await args.onRolledBack({
6664
+ request: args.request,
6665
+ sessionId: rollback.sessionId,
6666
+ headCheckpointId: rollback.head,
6667
+ invalidatedStructuredStreamIds: rollback.invalidatedStructuredStreamIds
6668
+ });
6669
+ } catch (cause) {
6670
+ await reportLifecycleError(args.onLifecycleError, {
6671
+ phase: "rolled-back",
6672
+ error: new ChatLifecycleHookError("rolled-back", cause)
6673
+ });
6674
+ }
6675
+ }
6676
+ return new Response(JSON.stringify(result), {
6677
+ headers: {
6678
+ "content-type": "application/json"
6679
+ }
6680
+ });
6681
+ }
6682
+ function createCheckpointRouteHandler(args) {
6683
+ const { agent, errorStatus } = args;
6684
+ return async function POST(request) {
6685
+ try {
6686
+ const body = parseCheckpointRequestBody(await readRequestJson(request));
6687
+ return await handleCheckpointRequestBody({
6688
+ agent,
6689
+ body,
6690
+ request,
6691
+ onForked: args.onForked,
6692
+ onRolledBack: args.onRolledBack,
6693
+ onLifecycleError: args.onLifecycleError
6694
+ });
6695
+ } catch (error) {
6696
+ return createFailureResponse(error, errorStatus);
6697
+ }
6698
+ };
6699
+ }
6700
+
6701
+ // src/adapters/http-client.ts
6702
+ var import_browser = require("@kortyx/stream/browser");
6703
+ async function* streamChatFromRoute(args) {
6704
+ yield* (0, import_browser.streamFromRoute)({
6705
+ endpoint: args.endpoint,
6706
+ ...args.fetchImpl ? { fetchImpl: args.fetchImpl } : {},
6707
+ ...args.headers ? { headers: args.headers } : {},
6708
+ body: {
6709
+ sessionId: args.sessionId,
6710
+ clientTurnId: args.clientTurnId,
6711
+ workflowId: args.workflowId,
6712
+ messages: args.messages
6713
+ }
6714
+ });
6715
+ }
6716
+
6717
+ // src/chat/create-agent.ts
6718
+ var import_node_path = require("path");
6719
+ var import_core5 = require("@kortyx/core");
6720
+ var import_providers = require("@kortyx/providers");
6721
+ var import_runtime5 = require("@kortyx/runtime");
6722
+ var import_zod5 = require("zod");
6723
+
6529
6724
  // src/execution/execute.ts
6725
+ var import_core4 = require("@kortyx/core");
6726
+ var import_errors6 = require("@kortyx/core/errors");
6727
+ var import_runtime3 = require("@kortyx/runtime");
6728
+ var import_zod3 = require("zod");
6530
6729
  var handleSchema = import_zod3.z.object({
6531
6730
  token: import_zod3.z.string().min(1),
6532
6731
  requestId: import_zod3.z.string().min(1),
@@ -6557,7 +6756,7 @@ function resultFromOutcome(outcome, runId, sessionId) {
6557
6756
  return {
6558
6757
  ...info,
6559
6758
  status: "failed",
6560
- error: (0, import_errors5.serializeFailure)(error)
6759
+ error: (0, import_errors6.serializeFailure)(error)
6561
6760
  };
6562
6761
  }
6563
6762
  if (outcome.cancelled)
@@ -6738,7 +6937,7 @@ async function resumeWorkflow(services, args) {
6738
6937
  );
6739
6938
  }).catch(
6740
6939
  (error) => reject(
6741
- error instanceof Error ? error : new import_errors5.KortyxError("RESUME_FAILED", "Resume failed.", {
6940
+ error instanceof Error ? error : new import_errors6.KortyxError("RESUME_FAILED", "Resume failed.", {
6742
6941
  category: "persistence",
6743
6942
  cause: error,
6744
6943
  safeMessage: "The saved execution could not be resumed."
@@ -6941,6 +7140,7 @@ function restoreWorkflowCallBranch(requests, telemetry, sourceRunId) {
6941
7140
  }
6942
7141
 
6943
7142
  // src/chat/process-chat.ts
7143
+ var import_errors7 = require("@kortyx/core/errors");
6944
7144
  var import_runtime4 = require("@kortyx/runtime");
6945
7145
 
6946
7146
  // src/utils/extract-latest-message.ts
@@ -6961,6 +7161,9 @@ async function streamChat({
6961
7161
  abortSignal,
6962
7162
  executionSignal,
6963
7163
  onExecution,
7164
+ clientTurnId,
7165
+ continueOnDisconnect,
7166
+ onResponseFinalized,
6964
7167
  options,
6965
7168
  sessionId,
6966
7169
  defaultWorkflowId,
@@ -6972,6 +7175,18 @@ async function streamChat({
6972
7175
  getProvider,
6973
7176
  applyResumeSelection
6974
7177
  }) {
7178
+ const suppliedSessionId = sessionId || options?.sessionId;
7179
+ if (onResponseFinalized && (!suppliedSessionId || !clientTurnId?.trim() || clientTurnId.length > 128)) {
7180
+ throw new import_errors7.KortyxError(
7181
+ "INVALID_REQUEST",
7182
+ "sessionId and clientTurnId are required with onResponseFinalized.",
7183
+ {
7184
+ category: "request",
7185
+ retryable: false,
7186
+ safeMessage: "This chat request needs a session and turn ID."
7187
+ }
7188
+ );
7189
+ }
6975
7190
  const config = await loadRuntimeConfig(options);
6976
7191
  let runtimeConfig2 = {
6977
7192
  ...config,
@@ -7010,6 +7225,9 @@ async function streamChat({
7010
7225
  abortSignal,
7011
7226
  executionSignal,
7012
7227
  onExecution,
7228
+ clientTurnId,
7229
+ continueOnDisconnect,
7230
+ onResponseFinalized,
7013
7231
  lastMessage: last,
7014
7232
  sessionId: resolvedSessionId,
7015
7233
  config: runtimeConfig2,
@@ -7083,6 +7301,9 @@ async function streamChat({
7083
7301
  abortSignal,
7084
7302
  executionSignal,
7085
7303
  onExecution,
7304
+ clientTurnId,
7305
+ continueOnDisconnect,
7306
+ onResponseFinalized,
7086
7307
  sessionId: resolvedSessionId,
7087
7308
  runId,
7088
7309
  graph,
@@ -7101,6 +7322,9 @@ var agentProcessOptionsSchema = import_zod5.z.object({
7101
7322
  onExecution: import_zod5.z.custom(
7102
7323
  (value) => typeof value === "function"
7103
7324
  ).optional(),
7325
+ clientTurnId: import_zod5.z.string().min(1).max(128).optional(),
7326
+ continueOnDisconnect: import_zod5.z.boolean().optional(),
7327
+ onResponseFinalized: import_zod5.z.custom((value) => typeof value === "function").optional(),
7104
7328
  limits: import_core5.ExecutionLimitsSchema.optional(),
7105
7329
  abortSignal: import_zod5.z.custom((value) => value instanceof AbortSignal).optional(),
7106
7330
  sessionId: import_zod5.z.string().optional(),
@@ -7247,6 +7471,9 @@ function createAgent(args) {
7247
7471
  abortSignal: parsedOptions?.abortSignal,
7248
7472
  executionSignal: parsedOptions?.executionSignal,
7249
7473
  onExecution: parsedOptions?.onExecution,
7474
+ clientTurnId: parsedOptions?.clientTurnId,
7475
+ continueOnDisconnect: parsedOptions?.continueOnDisconnect,
7476
+ onResponseFinalized: parsedOptions?.onResponseFinalized,
7250
7477
  options: parsedOptions,
7251
7478
  workflowRegistry: registry,
7252
7479
  ...knownWorkflowIds ? { knownWorkflowIds } : {},
@@ -7403,6 +7630,7 @@ function createAgent(args) {
7403
7630
  }
7404
7631
  // Annotate the CommonJS export names for ESM import in node:
7405
7632
  0 && (module.exports = {
7633
+ ChatLifecycleHookError,
7406
7634
  ExecutionRequestError,
7407
7635
  createAgent,
7408
7636
  createChatRouteHandler,