@kortyx/agent 0.26.1 → 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
@@ -3532,6 +3532,7 @@ ${tryStringify2(args.response)}`
3532
3532
  }
3533
3533
  };
3534
3534
  var shouldStreamReasonStructured = (args) => (args.emit ?? true) && shouldStreamStructured(args.structured);
3535
+ var usesNativeOutput = (schema) => schema?.["~kortyx"]?.nativeOutput === true;
3535
3536
  var resolveEffectiveReasoningIncludeThoughts = (args) => args.reasoning?.includeThoughts ?? args.model.options?.reasoning?.includeThoughts ?? false;
3536
3537
  var resolveEffectiveResponseFormatType = (args) => args.responseFormat?.type ?? args.model.options?.responseFormat?.type;
3537
3538
  var assertReasoningThoughtsCompatibility = (args) => {
@@ -3677,7 +3678,7 @@ ${tryStringify2(args.response)}`
3677
3678
  requestSchema: args.interrupt.requestSchema,
3678
3679
  mode: args.interrupt.mode ?? "required",
3679
3680
  ...args.outputSchema ? { outputSchema: args.outputSchema } : {}
3680
- }) : args.outputSchema ? withOutputGuardrails(
3681
+ }) : args.outputSchema ? usesNativeOutput(args.outputSchema) ? void 0 : withOutputGuardrails(
3681
3682
  withStructuredStreamHints(args.input, {
3682
3683
  ...setFieldPaths.length > 0 ? { setFieldPaths } : {},
3683
3684
  ...appendFieldPaths.length > 0 ? { appendFieldPaths } : {},
@@ -3949,7 +3950,7 @@ ${tryStringify2(args.response)}`
3949
3950
  return [getValue(), setValue];
3950
3951
  }
3951
3952
  var import_core7 = require("@kortyx/core");
3952
- var import_errors7 = require("@kortyx/core/errors");
3953
+ var import_errors72 = require("@kortyx/core/errors");
3953
3954
  var ParallelChildWaiting = class extends Error {
3954
3955
  name = "ParallelChildWaiting";
3955
3956
  };
@@ -3963,14 +3964,14 @@ ${tryStringify2(args.response)}`
3963
3964
  );
3964
3965
  this.results = results;
3965
3966
  this.name = "ParallelError";
3966
- this.failure = (0, import_errors7.serializeFailure)({
3967
+ this.failure = (0, import_errors72.serializeFailure)({
3967
3968
  version: 1,
3968
3969
  code: this.code,
3969
3970
  category: "workflow",
3970
3971
  message: this.message,
3971
3972
  retryable: null,
3972
3973
  results: results.map(
3973
- (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) }
3974
3975
  )
3975
3976
  });
3976
3977
  }
@@ -3978,7 +3979,7 @@ ${tryStringify2(args.response)}`
3978
3979
  code = "PARALLEL_FAILED";
3979
3980
  failure;
3980
3981
  toJSON() {
3981
- return (0, import_errors7.serializeFailure)(this);
3982
+ return (0, import_errors72.serializeFailure)(this);
3982
3983
  }
3983
3984
  };
3984
3985
  function registerWorkflowTask(id) {
@@ -4123,11 +4124,11 @@ ${tryStringify2(args.response)}`
4123
4124
  }
4124
4125
  var import_node_crypto6 = require("crypto");
4125
4126
  var import_core8 = require("@kortyx/core");
4126
- var import_errors8 = require("@kortyx/core/errors");
4127
- var WorkflowCallError = class extends import_errors8.KortyxError {
4127
+ var import_errors82 = require("@kortyx/core/errors");
4128
+ var WorkflowCallError = class extends import_errors82.KortyxError {
4128
4129
  constructor(workflow, message, cause, failure) {
4129
- const detail = (0, import_errors8.serializeFailure)(
4130
- failure ?? cause ?? new import_errors8.WorkflowContractError(message)
4130
+ const detail = (0, import_errors82.serializeFailure)(
4131
+ failure ?? cause ?? new import_errors82.WorkflowContractError(message)
4131
4132
  );
4132
4133
  super(detail.code, `Workflow '${workflow}': ${message}`, {
4133
4134
  ...detail,
@@ -4148,12 +4149,12 @@ ${tryStringify2(args.response)}`
4148
4149
  if (v === null || typeof v === "string" || typeof v === "boolean") return v;
4149
4150
  if (typeof v === "number" && Number.isFinite(v)) return v;
4150
4151
  if (typeof v !== "object" || !v || seen.has(v)) {
4151
- throw new import_errors8.WorkflowContractError(
4152
+ throw new import_errors82.WorkflowContractError(
4152
4153
  "Workflow input and output must be JSON serializable."
4153
4154
  );
4154
4155
  }
4155
4156
  if (!Array.isArray(v) && Object.getPrototypeOf(v) !== Object.prototype) {
4156
- throw new import_errors8.WorkflowContractError(
4157
+ throw new import_errors82.WorkflowContractError(
4157
4158
  "Workflow input and output must contain plain JSON objects."
4158
4159
  );
4159
4160
  }
@@ -4174,7 +4175,7 @@ ${tryStringify2(args.response)}`
4174
4175
  useWorkflow: (args) => {
4175
4176
  const workflow = workflows[args.workflow];
4176
4177
  if (!workflow || workflow.id !== args.workflow)
4177
- throw new import_errors8.WorkflowContractError(
4178
+ throw new import_errors82.WorkflowContractError(
4178
4179
  "Workflow registry keys must match definition ids."
4179
4180
  );
4180
4181
  return useWorkflow({ ...args, workflow });
@@ -4200,23 +4201,23 @@ ${tryStringify2(args.response)}`
4200
4201
  (0, import_core8.throwIfExecutionAborted)(ctx.node.abortSignal);
4201
4202
  const service = ctx.node.callWorkflow;
4202
4203
  if (!service)
4203
- throw new import_errors8.WorkflowContractError(
4204
+ throw new import_errors82.WorkflowContractError(
4204
4205
  "useWorkflow requires an agent workflow registry."
4205
4206
  );
4206
4207
  if (!args.id.trim())
4207
- throw new import_errors8.WorkflowContractError(
4208
+ throw new import_errors82.WorkflowContractError(
4208
4209
  "useWorkflow requires a stable, nonempty id."
4209
4210
  );
4210
4211
  if (ctx.workflowCallActive && !task.group)
4211
- throw new import_errors8.WorkflowContractError(
4212
+ throw new import_errors82.WorkflowContractError(
4212
4213
  "Concurrent workflow calls require parallel([...]); otherwise await each call."
4213
4214
  );
4214
4215
  if (ctx.workflowCallIds.has(args.id))
4215
- throw new import_errors8.WorkflowContractError(
4216
+ throw new import_errors82.WorkflowContractError(
4216
4217
  `Duplicate workflow call id '${args.id}' in one node activation.`
4217
4218
  );
4218
4219
  if (ctx.workflowCallIds.size >= 64)
4219
- throw new import_errors8.WorkflowContractError(
4220
+ throw new import_errors82.WorkflowContractError(
4220
4221
  "A node may call at most 64 child workflows per activation."
4221
4222
  );
4222
4223
  ctx.workflowCallIds.add(args.id);
@@ -4297,7 +4298,7 @@ ${tryStringify2(args.response)}`
4297
4298
  workflow,
4298
4299
  record.error ?? "child failed",
4299
4300
  void 0,
4300
- record.failure ?? (0, import_errors8.serializeFailure)(void 0)
4301
+ record.failure ?? (0, import_errors82.serializeFailure)(void 0)
4301
4302
  );
4302
4303
  if (task.group && record.status === "interrupted" && record.interrupts.at(-1)?.response === void 0)
4303
4304
  throw new ParallelChildWaiting();
@@ -4333,7 +4334,7 @@ ${tryStringify2(args.response)}`
4333
4334
  throw error;
4334
4335
  }
4335
4336
  record.status = "failed";
4336
- record.failure = (0, import_errors8.serializeFailure)(error);
4337
+ record.failure = (0, import_errors82.serializeFailure)(error);
4337
4338
  record.error = record.failure.message;
4338
4339
  report("failed", { error: "Child workflow failed" });
4339
4340
  throw new WorkflowCallError(
@@ -4394,6 +4395,7 @@ ${tryStringify2(args.response)}`
4394
4395
  // src/index.ts
4395
4396
  var index_exports = {};
4396
4397
  __export(index_exports, {
4398
+ ChatLifecycleHookError: () => ChatLifecycleHookError,
4397
4399
  ExecutionRequestError: () => ExecutionRequestError,
4398
4400
  createAgent: () => createAgent,
4399
4401
  createChatRouteHandler: () => createChatRouteHandler,
@@ -4416,183 +4418,29 @@ __export(index_exports, {
4416
4418
  module.exports = __toCommonJS(index_exports);
4417
4419
 
4418
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
4419
4426
  var import_errors = require("@kortyx/core/errors");
4420
- var import_stream = require("@kortyx/stream");
4421
- var import_zod = require("zod");
4422
- var chatMessageSchema = import_zod.z.object({
4423
- role: import_zod.z.enum(["user", "assistant", "system"]),
4424
- content: import_zod.z.string(),
4425
- metadata: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
4426
- id: import_zod.z.string().optional(),
4427
- timestamp: import_zod.z.number().refine(Number.isFinite).optional()
4428
- }).strict();
4429
- var chatRequestBodySchema = import_zod.z.looseObject({
4430
- sessionId: import_zod.z.string().optional(),
4431
- workflowId: import_zod.z.string().optional(),
4432
- stream: import_zod.z.boolean().optional(),
4433
- context: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional(),
4434
- messages: import_zod.z.array(chatMessageSchema)
4435
- });
4436
- var checkpointRequestBodySchema = import_zod.z.discriminatedUnion("action", [
4437
- import_zod.z.object({
4438
- action: import_zod.z.literal("list"),
4439
- sessionId: import_zod.z.string().min(1)
4440
- }).strict(),
4441
- import_zod.z.object({
4442
- action: import_zod.z.literal("get"),
4443
- checkpointId: import_zod.z.string().min(1)
4444
- }).strict(),
4445
- import_zod.z.object({
4446
- action: import_zod.z.literal("rollback"),
4447
- checkpointId: import_zod.z.string().min(1)
4448
- }).strict(),
4449
- import_zod.z.object({
4450
- action: import_zod.z.literal("fork"),
4451
- checkpointId: import_zod.z.string().min(1),
4452
- newSessionId: import_zod.z.string().min(1).optional()
4453
- }).strict()
4454
- ]);
4455
- async function readRequestJson(request) {
4456
- try {
4457
- const value = await request.json();
4458
- if (!value || typeof value !== "object" || Array.isArray(value))
4459
- throw new Error("Expected a JSON object.");
4460
- return value;
4461
- } catch (cause) {
4462
- throw new import_errors.KortyxError(
4463
- "INVALID_REQUEST",
4464
- "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}`,
4465
4433
  {
4466
- category: "request",
4467
- retryable: false,
4468
- safeMessage: "The request body must contain a valid JSON object.",
4434
+ category: "persistence",
4435
+ retryable: null,
4436
+ safeMessage: "Chat state could not be saved.",
4469
4437
  cause
4470
4438
  }
4471
4439
  );
4440
+ this.name = "ChatLifecycleHookError";
4441
+ this.phase = phase;
4472
4442
  }
4473
- }
4474
- function createFailureResponse(error, status) {
4475
- const failure = (0, import_errors.serializeFailure)(error);
4476
- return Response.json(
4477
- { error: failure.message, failure },
4478
- { status: status ?? (0, import_errors.failureHttpStatus)(failure) }
4479
- );
4480
- }
4481
- function parseChatRequestBody(value) {
4482
- const parsed = chatRequestBodySchema.safeParse(value);
4483
- if (!parsed.success) {
4484
- const [firstIssue] = parsed.error.issues;
4485
- throw new import_errors.KortyxError("INVALID_REQUEST", firstIssue.message, {
4486
- category: "request",
4487
- retryable: false,
4488
- safeMessage: "The request body is invalid."
4489
- });
4490
- }
4491
- const sessionId = parsed.data.sessionId?.trim();
4492
- const workflowId = parsed.data.workflowId?.trim();
4493
- return {
4494
- ...sessionId ? { sessionId } : {},
4495
- ...workflowId ? { workflowId } : {},
4496
- ...typeof parsed.data.stream === "boolean" ? { stream: parsed.data.stream } : {},
4497
- ...parsed.data.context ? { context: parsed.data.context } : {},
4498
- messages: parsed.data.messages
4499
- };
4500
- }
4501
- function parseCheckpointRequestBody(value) {
4502
- const parsed = checkpointRequestBodySchema.safeParse(value);
4503
- if (parsed.success) return parsed.data;
4504
- const [firstIssue] = parsed.error.issues;
4505
- throw new import_errors.KortyxError("INVALID_REQUEST", firstIssue.message, {
4506
- category: "request",
4507
- retryable: false,
4508
- safeMessage: "The request body is invalid."
4509
- });
4510
- }
4511
- async function handleChatRequestBody(args) {
4512
- const { agent, body } = args;
4513
- const stream = await agent.streamChat(body.messages, {
4514
- ...args.onExecution ? { onExecution: args.onExecution } : {},
4515
- ...args.abortSignal ? { abortSignal: args.abortSignal } : {},
4516
- sessionId: body.sessionId,
4517
- workflowId: body.workflowId,
4518
- context: body.context
4519
- });
4520
- if (body.stream === false) {
4521
- const buffered = await (0, import_stream.collectBufferedStream)(stream);
4522
- return new Response(JSON.stringify(buffered), {
4523
- headers: {
4524
- "content-type": "application/json"
4525
- }
4526
- });
4527
- }
4528
- return (0, import_stream.toSSE)(stream);
4529
- }
4530
- function createChatRouteHandler(args) {
4531
- const { agent, errorStatus } = args;
4532
- return async function POST(request) {
4533
- try {
4534
- const body = parseChatRequestBody(await readRequestJson(request));
4535
- return await handleChatRequestBody({
4536
- agent,
4537
- body,
4538
- abortSignal: request.signal,
4539
- onExecution: args.onExecution
4540
- });
4541
- } catch (error) {
4542
- return createFailureResponse(error, errorStatus);
4543
- }
4544
- };
4545
- }
4546
- async function handleCheckpointRequestBody(args) {
4547
- const { agent, body } = args;
4548
- 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, {
4549
- ...body.newSessionId ? { newSessionId: body.newSessionId } : {}
4550
- });
4551
- return new Response(JSON.stringify(result), {
4552
- headers: {
4553
- "content-type": "application/json"
4554
- }
4555
- });
4556
- }
4557
- function createCheckpointRouteHandler(args) {
4558
- const { agent, errorStatus } = args;
4559
- return async function POST(request) {
4560
- try {
4561
- const body = parseCheckpointRequestBody(await readRequestJson(request));
4562
- return await handleCheckpointRequestBody({ agent, body });
4563
- } catch (error) {
4564
- return createFailureResponse(error, errorStatus);
4565
- }
4566
- };
4567
- }
4568
-
4569
- // src/adapters/http-client.ts
4570
- var import_browser = require("@kortyx/stream/browser");
4571
- async function* streamChatFromRoute(args) {
4572
- yield* (0, import_browser.streamFromRoute)({
4573
- endpoint: args.endpoint,
4574
- ...args.fetchImpl ? { fetchImpl: args.fetchImpl } : {},
4575
- ...args.headers ? { headers: args.headers } : {},
4576
- body: {
4577
- sessionId: args.sessionId,
4578
- workflowId: args.workflowId,
4579
- messages: args.messages
4580
- }
4581
- });
4582
- }
4583
-
4584
- // src/chat/create-agent.ts
4585
- var import_node_path = require("path");
4586
- var import_core5 = require("@kortyx/core");
4587
- var import_providers = require("@kortyx/providers");
4588
- var import_runtime5 = require("@kortyx/runtime");
4589
- var import_zod5 = require("zod");
4590
-
4591
- // src/execution/execute.ts
4592
- var import_core4 = require("@kortyx/core");
4593
- var import_errors5 = require("@kortyx/core/errors");
4594
- var import_runtime3 = require("@kortyx/runtime");
4595
- var import_zod3 = require("zod");
4443
+ };
4596
4444
 
4597
4445
  // src/interrupt/resume-handler.ts
4598
4446
  var import_core3 = require("@kortyx/core");
@@ -4618,11 +4466,12 @@ var import_core2 = require("@kortyx/core");
4618
4466
  var import_errors4 = require("@kortyx/core/errors");
4619
4467
  var import_internal = __toESM(require_internal());
4620
4468
  var import_runtime = require("@kortyx/runtime");
4469
+ var import_stream = require("@kortyx/stream");
4621
4470
  var import_langgraph = require("@langchain/langgraph");
4622
4471
 
4623
4472
  // src/execution/contracts.ts
4624
4473
  var import_errors3 = require("@kortyx/core/errors");
4625
- var import_zod2 = require("zod");
4474
+ var import_zod = require("zod");
4626
4475
  async function resolveExecutionWorkflow(select, reference) {
4627
4476
  const id = typeof reference === "string" ? reference : reference.id;
4628
4477
  let workflow;
@@ -4654,12 +4503,12 @@ async function resolveExecutionWorkflow(select, reference) {
4654
4503
  }
4655
4504
  function parseExecutionInput(workflow, input) {
4656
4505
  try {
4657
- import_zod2.z.json().parse(input);
4506
+ import_zod.z.json().parse(input);
4658
4507
  const parsed = workflow.inputSchema ? workflow.inputSchema.parse(input) : input;
4659
- import_zod2.z.json().parse(parsed);
4508
+ import_zod.z.json().parse(parsed);
4660
4509
  return parsed;
4661
4510
  } catch (error) {
4662
- if (!(error instanceof import_zod2.z.ZodError)) throw error;
4511
+ if (!(error instanceof import_zod.z.ZodError)) throw error;
4663
4512
  throw new ExecutionRequestError("INVALID_INPUT", error.message, error);
4664
4513
  }
4665
4514
  }
@@ -4677,9 +4526,9 @@ async function validateExecutionOutput(state, select) {
4677
4526
  for (const schema of /* @__PURE__ */ new Set([terminal.outputSchema, root.outputSchema])) {
4678
4527
  if (schema) data = schema.parse(data);
4679
4528
  }
4680
- if (terminal.outputSchema || root.outputSchema) import_zod2.z.json().parse(data);
4529
+ if (terminal.outputSchema || root.outputSchema) import_zod.z.json().parse(data);
4681
4530
  } catch (error) {
4682
- if (!(error instanceof import_zod2.z.ZodError)) throw error;
4531
+ if (!(error instanceof import_zod.z.ZodError)) throw error;
4683
4532
  const failure = new import_errors3.ValidationError(
4684
4533
  "INVALID_OUTPUT",
4685
4534
  `Workflow output validation failed: ${error.message}`,
@@ -5090,6 +4939,9 @@ async function orchestrateGraphStream({
5090
4939
  abortSignal: requestSignal,
5091
4940
  executionSignal,
5092
4941
  onExecution,
4942
+ clientTurnId,
4943
+ continueOnDisconnect = false,
4944
+ onResponseFinalized,
5093
4945
  emitOutput = true,
5094
4946
  onOutcome,
5095
4947
  selectWorkflow,
@@ -5098,12 +4950,34 @@ async function orchestrateGraphStream({
5098
4950
  executionLeaseRelease
5099
4951
  }) {
5100
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
+ };
5101
4972
  const write = (chunk) => {
5102
4973
  if (isRecord3(chunk) && chunk.type === "done" && isGraphState(chunk.data)) {
5103
4974
  const { __kortyxParallelGraph: _journal, ...runtime } = chunk.data.runtime;
5104
4975
  chunk = { ...chunk, data: { ...chunk.data, runtime } };
5105
4976
  }
5106
- 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
+ }
5107
4981
  };
5108
4982
  let outcomeState = state;
5109
4983
  let outcomeError;
@@ -5118,7 +4992,7 @@ async function orchestrateGraphStream({
5118
4992
  const response = createResponseLifecycle({
5119
4993
  enabled: emitOutput,
5120
4994
  restored: initialConfig.responseCompleted === true,
5121
- requestSignal,
4995
+ requestSignal: continueOnDisconnect ? void 0 : requestSignal,
5122
4996
  executionSignal: executionSignal ? AbortSignal.any([executionSignal, leaseAbort.signal]) : leaseAbort.signal,
5123
4997
  onClosed: () => {
5124
4998
  out.write({ type: "done" });
@@ -5182,11 +5056,12 @@ async function orchestrateGraphStream({
5182
5056
  },
5183
5057
  flush: true
5184
5058
  });
5059
+ await finalizeVisibleResponse("completed");
5185
5060
  }
5186
5061
  });
5187
5062
  const abortSignal = response.signal;
5188
5063
  out.on("close", () => {
5189
- if (!finished) response.disconnect();
5064
+ if (!finished && !continueOnDisconnect) response.disconnect();
5190
5065
  });
5191
5066
  let structuredSeq = 0;
5192
5067
  const debugEnabled = Boolean(config?.features?.tracing);
@@ -5939,6 +5814,9 @@ async function orchestrateGraphStream({
5939
5814
  );
5940
5815
  }
5941
5816
  }
5817
+ await finalizeVisibleResponse(
5818
+ outcomeError ? "failed" : shouldKeepFrameworkState ? "interrupted" : "completed"
5819
+ );
5942
5820
  write({ type: "done", data: workflowFinalState });
5943
5821
  out.end();
5944
5822
  return;
@@ -6016,6 +5894,9 @@ async function orchestrateGraphStream({
6016
5894
  });
6017
5895
  finished = true;
6018
5896
  runTraceSpan.end?.(runTraceEndArgs());
5897
+ await finalizeVisibleResponse(
5898
+ outcomeError ? "failed" : pendingRecordToken || naturalFinalState.awaitingHumanInput ? "interrupted" : "completed"
5899
+ );
6019
5900
  write({ type: "done", data: naturalFinalState });
6020
5901
  out.end();
6021
5902
  return;
@@ -6076,6 +5957,7 @@ async function orchestrateGraphStream({
6076
5957
  fallbackRunSpan?.setAttributes?.({ "kortyx.run.cancelled": true });
6077
5958
  fallbackRunSpan?.end?.();
6078
5959
  write({ type: "cancelled", runId, reason: "Execution cancelled." });
5960
+ await finalizeVisibleResponse("cancelled");
6079
5961
  write({ type: "done" });
6080
5962
  finished = true;
6081
5963
  out.end();
@@ -6222,6 +6104,9 @@ async function orchestrateGraphStream({
6222
6104
  console.error("[execution:releaseLease]", (0, import_errors4.serializeFailure)(error));
6223
6105
  }
6224
6106
  response.dispose();
6107
+ await finalizeVisibleResponse(
6108
+ outcomeError ? (0, import_core2.isExecutionCancelled)(outcomeError) ? "cancelled" : "failed" : pendingRecordToken || outcomeState.awaitingHumanInput ? "interrupted" : "completed"
6109
+ );
6225
6110
  onOutcome?.({
6226
6111
  state: outcomeState,
6227
6112
  pending: pendingRecordToken ? activePendingRequests.get(pendingRecordToken) : void 0,
@@ -6271,6 +6156,9 @@ async function tryPrepareResumeStream({
6271
6156
  abortSignal,
6272
6157
  executionSignal,
6273
6158
  onExecution,
6159
+ clientTurnId,
6160
+ continueOnDisconnect,
6161
+ onResponseFinalized,
6274
6162
  lastMessage,
6275
6163
  meta: suppliedMeta,
6276
6164
  emitOutput,
@@ -6489,6 +6377,9 @@ async function tryPrepareResumeStream({
6489
6377
  executionSignal: continuedSignal ? AbortSignal.any([continuedSignal, leaseAbort.signal]) : leaseAbort.signal,
6490
6378
  executionLeaseRelease: releaseLease,
6491
6379
  onExecution,
6380
+ clientTurnId,
6381
+ continueOnDisconnect,
6382
+ onResponseFinalized,
6492
6383
  emitOutput,
6493
6384
  onOutcome,
6494
6385
  sessionId,
@@ -6525,7 +6416,316 @@ async function tryPrepareResumeStream({
6525
6416
  }
6526
6417
  }
6527
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
+
6528
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");
6529
6729
  var handleSchema = import_zod3.z.object({
6530
6730
  token: import_zod3.z.string().min(1),
6531
6731
  requestId: import_zod3.z.string().min(1),
@@ -6556,7 +6756,7 @@ function resultFromOutcome(outcome, runId, sessionId) {
6556
6756
  return {
6557
6757
  ...info,
6558
6758
  status: "failed",
6559
- error: (0, import_errors5.serializeFailure)(error)
6759
+ error: (0, import_errors6.serializeFailure)(error)
6560
6760
  };
6561
6761
  }
6562
6762
  if (outcome.cancelled)
@@ -6737,7 +6937,7 @@ async function resumeWorkflow(services, args) {
6737
6937
  );
6738
6938
  }).catch(
6739
6939
  (error) => reject(
6740
- 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.", {
6741
6941
  category: "persistence",
6742
6942
  cause: error,
6743
6943
  safeMessage: "The saved execution could not be resumed."
@@ -6940,6 +7140,7 @@ function restoreWorkflowCallBranch(requests, telemetry, sourceRunId) {
6940
7140
  }
6941
7141
 
6942
7142
  // src/chat/process-chat.ts
7143
+ var import_errors7 = require("@kortyx/core/errors");
6943
7144
  var import_runtime4 = require("@kortyx/runtime");
6944
7145
 
6945
7146
  // src/utils/extract-latest-message.ts
@@ -6960,6 +7161,9 @@ async function streamChat({
6960
7161
  abortSignal,
6961
7162
  executionSignal,
6962
7163
  onExecution,
7164
+ clientTurnId,
7165
+ continueOnDisconnect,
7166
+ onResponseFinalized,
6963
7167
  options,
6964
7168
  sessionId,
6965
7169
  defaultWorkflowId,
@@ -6971,6 +7175,18 @@ async function streamChat({
6971
7175
  getProvider,
6972
7176
  applyResumeSelection
6973
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
+ }
6974
7190
  const config = await loadRuntimeConfig(options);
6975
7191
  let runtimeConfig2 = {
6976
7192
  ...config,
@@ -7009,6 +7225,9 @@ async function streamChat({
7009
7225
  abortSignal,
7010
7226
  executionSignal,
7011
7227
  onExecution,
7228
+ clientTurnId,
7229
+ continueOnDisconnect,
7230
+ onResponseFinalized,
7012
7231
  lastMessage: last,
7013
7232
  sessionId: resolvedSessionId,
7014
7233
  config: runtimeConfig2,
@@ -7082,6 +7301,9 @@ async function streamChat({
7082
7301
  abortSignal,
7083
7302
  executionSignal,
7084
7303
  onExecution,
7304
+ clientTurnId,
7305
+ continueOnDisconnect,
7306
+ onResponseFinalized,
7085
7307
  sessionId: resolvedSessionId,
7086
7308
  runId,
7087
7309
  graph,
@@ -7100,6 +7322,9 @@ var agentProcessOptionsSchema = import_zod5.z.object({
7100
7322
  onExecution: import_zod5.z.custom(
7101
7323
  (value) => typeof value === "function"
7102
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(),
7103
7328
  limits: import_core5.ExecutionLimitsSchema.optional(),
7104
7329
  abortSignal: import_zod5.z.custom((value) => value instanceof AbortSignal).optional(),
7105
7330
  sessionId: import_zod5.z.string().optional(),
@@ -7246,6 +7471,9 @@ function createAgent(args) {
7246
7471
  abortSignal: parsedOptions?.abortSignal,
7247
7472
  executionSignal: parsedOptions?.executionSignal,
7248
7473
  onExecution: parsedOptions?.onExecution,
7474
+ clientTurnId: parsedOptions?.clientTurnId,
7475
+ continueOnDisconnect: parsedOptions?.continueOnDisconnect,
7476
+ onResponseFinalized: parsedOptions?.onResponseFinalized,
7249
7477
  options: parsedOptions,
7250
7478
  workflowRegistry: registry,
7251
7479
  ...knownWorkflowIds ? { knownWorkflowIds } : {},
@@ -7402,6 +7630,7 @@ function createAgent(args) {
7402
7630
  }
7403
7631
  // Annotate the CommonJS export names for ESM import in node:
7404
7632
  0 && (module.exports = {
7633
+ ChatLifecycleHookError,
7405
7634
  ExecutionRequestError,
7406
7635
  createAgent,
7407
7636
  createChatRouteHandler,