@kortyx/agent 0.4.0 → 0.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.0](https://github.com/kortyx-io/kortyx/compare/agent-v0.4.1...agent-v0.5.0) (2026-02-17)
4
+
5
+
6
+ ### Features
7
+
8
+ * **agent:** strict createAgent and unify useReason streaming ([6725f97](https://github.com/kortyx-io/kortyx/commit/6725f976fc32083fb7d2590f1353d16a99afb46f))
9
+ * **kortyx:** add browser-safe chat streaming adapters ([c7eb98e](https://github.com/kortyx-io/kortyx/commit/c7eb98e06781a708c79f143ba725efb69d35709e))
10
+ * make createAgent strict/declarative and split providers ([1d252f2](https://github.com/kortyx-io/kortyx/commit/1d252f2dcd51622f821715c0fffb13733aeb3cae))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **agent:** remove eager provider registration guard ([8da4059](https://github.com/kortyx-io/kortyx/commit/8da40592d55bb31800a4d85edcd49795ddf09a29))
16
+ * **agent:** simplify stream transformer and interrupt orchestration ([d0b5847](https://github.com/kortyx-io/kortyx/commit/d0b58475e139e070fd9ca1a68b71395642ac32d5))
17
+
18
+ ## [0.4.1](https://github.com/kortyx-io/kortyx/compare/agent-v0.4.0...agent-v0.4.1) (2026-02-15)
19
+
20
+
21
+ ### Bug Fixes
22
+
23
+ * remove kortyx.config support and update docs ([ecb3f8d](https://github.com/kortyx-io/kortyx/commit/ecb3f8d53a017cc84bf8db3e125a6f711f5d4013))
24
+ * remove kortyx.config support and update docs ([aa4a70a](https://github.com/kortyx-io/kortyx/commit/aa4a70a034dfec7bca829793f1729c51be018ae2))
25
+
3
26
  ## [0.4.0](https://github.com/kortyx-io/Kortyx/compare/agent-v0.3.0...agent-v0.4.0) (2026-02-07)
4
27
 
5
28
 
@@ -0,0 +1,12 @@
1
+ import { type StreamChunk } from "@kortyx/stream/browser";
2
+ import type { ChatMessage } from "../types/chat-message";
3
+ export interface StreamChatFromRouteArgs {
4
+ endpoint: string;
5
+ sessionId: string;
6
+ workflowId?: string | undefined;
7
+ messages: ChatMessage[];
8
+ fetchImpl?: typeof fetch;
9
+ headers?: Record<string, string> | undefined;
10
+ }
11
+ export declare function streamChatFromRoute(args: StreamChatFromRouteArgs): AsyncGenerator<StreamChunk, void, void>;
12
+ //# sourceMappingURL=http-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-client.d.ts","sourceRoot":"","sources":["../../src/adapters/http-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAmB,MAAM,wBAAwB,CAAC;AAC3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;CAC9C;AAED,wBAAuB,mBAAmB,CACxC,IAAI,EAAE,uBAAuB,GAC5B,cAAc,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,CAAC,CAWzC"}
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.streamChatFromRoute = streamChatFromRoute;
4
+ const browser_1 = require("@kortyx/stream/browser");
5
+ async function* streamChatFromRoute(args) {
6
+ yield* (0, browser_1.streamFromRoute)({
7
+ endpoint: args.endpoint,
8
+ ...(args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}),
9
+ ...(args.headers ? { headers: args.headers } : {}),
10
+ body: {
11
+ sessionId: args.sessionId,
12
+ ...(args.workflowId ? { workflowId: args.workflowId } : {}),
13
+ messages: args.messages,
14
+ },
15
+ });
16
+ }
@@ -0,0 +1,17 @@
1
+ import type { Agent } from "../chat/create-agent";
2
+ import type { ChatMessage } from "../types/chat-message";
3
+ export type ChatRequestBody = {
4
+ sessionId: string;
5
+ workflowId?: string | undefined;
6
+ messages: ChatMessage[];
7
+ };
8
+ export declare function parseChatRequestBody(value: unknown): ChatRequestBody;
9
+ export declare function processChatRequestBody(args: {
10
+ agent: Agent;
11
+ body: ChatRequestBody;
12
+ }): Promise<Response>;
13
+ export declare function createChatRouteHandler(args: {
14
+ agent: Agent;
15
+ errorStatus?: number | undefined;
16
+ }): (request: Request) => Promise<Response>;
17
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/adapters/http.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEzD,MAAM,MAAM,eAAe,GAAG;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,QAAQ,EAAE,WAAW,EAAE,CAAC;CACzB,CAAC;AA4BF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAapE;AAED,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IACjD,KAAK,EAAE,KAAK,CAAC;IACb,IAAI,EAAE,eAAe,CAAC;CACvB,GAAG,OAAO,CAAC,QAAQ,CAAC,CAMpB;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,KAAK,EAAE,KAAK,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAqB1C"}
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseChatRequestBody = parseChatRequestBody;
4
+ exports.processChatRequestBody = processChatRequestBody;
5
+ exports.createChatRouteHandler = createChatRouteHandler;
6
+ const zod_1 = require("zod");
7
+ const chatMessageSchema = zod_1.z
8
+ .object({
9
+ role: zod_1.z.enum(["user", "assistant", "system"]),
10
+ content: zod_1.z.string(),
11
+ metadata: zod_1.z.record(zod_1.z.unknown()).optional(),
12
+ id: zod_1.z.string().optional(),
13
+ timestamp: zod_1.z.number().finite().optional(),
14
+ })
15
+ .strict();
16
+ const chatRequestBodySchema = zod_1.z
17
+ .object({
18
+ sessionId: zod_1.z
19
+ .string()
20
+ .transform((value) => value.trim())
21
+ .refine((value) => value.length > 0, {
22
+ message: "`sessionId` is required.",
23
+ }),
24
+ workflowId: zod_1.z.string().optional(),
25
+ messages: zod_1.z.array(chatMessageSchema),
26
+ })
27
+ .strict();
28
+ const toErrorMessage = (error) => error instanceof Error ? error.message : String(error);
29
+ function parseChatRequestBody(value) {
30
+ const parsed = chatRequestBodySchema.safeParse(value);
31
+ if (!parsed.success) {
32
+ throw new Error(parsed.error.issues[0]?.message ?? "Invalid chat request.");
33
+ }
34
+ const workflowId = parsed.data.workflowId?.trim();
35
+ return {
36
+ sessionId: parsed.data.sessionId,
37
+ ...(workflowId ? { workflowId } : {}),
38
+ messages: parsed.data.messages,
39
+ };
40
+ }
41
+ async function processChatRequestBody(args) {
42
+ const { agent, body } = args;
43
+ return agent.processChat(body.messages, {
44
+ sessionId: body.sessionId,
45
+ ...(body.workflowId ? { workflowId: body.workflowId } : {}),
46
+ });
47
+ }
48
+ function createChatRouteHandler(args) {
49
+ const { agent, errorStatus = 400 } = args;
50
+ return async function POST(request) {
51
+ try {
52
+ const body = parseChatRequestBody(await request.json());
53
+ return await processChatRequestBody({ agent, body });
54
+ }
55
+ catch (error) {
56
+ return new Response(JSON.stringify({
57
+ error: toErrorMessage(error),
58
+ }), {
59
+ status: errorStatus,
60
+ headers: {
61
+ "content-type": "application/json",
62
+ },
63
+ });
64
+ }
65
+ };
66
+ }
@@ -0,0 +1,3 @@
1
+ export type { StreamChatFromRouteArgs } from "./adapters/http-client";
2
+ export { streamChatFromRoute } from "./adapters/http-client";
3
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAGA,YAAY,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.streamChatFromRoute = void 0;
4
+ var http_client_1 = require("./adapters/http-client");
5
+ Object.defineProperty(exports, "streamChatFromRoute", { enumerable: true, get: function () { return http_client_1.streamChatFromRoute; } });
@@ -1,16 +1,33 @@
1
- import type { KortyxConfig, WorkflowRegistry } from "@kortyx/runtime";
2
- import type { SelectWorkflowFn } from "../orchestrator";
1
+ import type { WorkflowDefinition } from "@kortyx/core";
2
+ import { type GetProviderFn } from "@kortyx/providers";
3
+ import type { FrameworkAdapter, WorkflowRegistry } from "@kortyx/runtime";
3
4
  import type { ChatMessage } from "../types/chat-message";
4
- import type { ProcessChatArgs } from "./process-chat";
5
- export interface CreateAgentArgs<Config extends Record<string, unknown>, Options> extends Omit<ProcessChatArgs<Config, Options>, "messages" | "options" | "selectWorkflow" | "workflowRegistry"> {
5
+ export interface AgentSessionConfig {
6
+ id?: string | undefined;
7
+ }
8
+ export interface AgentMemoryConfig {
9
+ enabled?: boolean | undefined;
10
+ namespace?: string | undefined;
11
+ ttlMs?: number | undefined;
12
+ }
13
+ export interface AgentProcessOptions {
14
+ sessionId?: string | undefined;
15
+ workflowId?: string | undefined;
16
+ workflow?: string | undefined;
17
+ }
18
+ export interface CreateAgentArgs {
19
+ getProvider?: GetProviderFn | undefined;
20
+ workflows?: WorkflowDefinition[];
6
21
  workflowsDir?: string;
7
22
  workflowRegistry?: WorkflowRegistry;
8
- selectWorkflow?: SelectWorkflowFn;
9
23
  fallbackWorkflowId?: string;
10
- config?: KortyxConfig;
11
- configPath?: string;
24
+ defaultWorkflowId?: string;
25
+ frameworkAdapter?: FrameworkAdapter;
26
+ session?: AgentSessionConfig;
27
+ memory?: AgentMemoryConfig;
28
+ }
29
+ export interface Agent {
30
+ processChat: (messages: ChatMessage[], options?: AgentProcessOptions) => Promise<Response>;
12
31
  }
13
- export declare function createAgent<Config extends Record<string, unknown>, Options = unknown>(args: CreateAgentArgs<Config, Options>): {
14
- processChat: (messages: ChatMessage[], options?: Options) => Promise<Response>;
15
- };
32
+ export declare function createAgent(args: CreateAgentArgs): Agent;
16
33
  //# sourceMappingURL=create-agent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-agent.d.ts","sourceRoot":"","sources":["../../src/chat/create-agent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,YAAY,EACZ,gBAAgB,EACjB,MAAM,iBAAiB,CAAC;AAMzB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGtD,MAAM,WAAW,eAAe,CAC9B,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACtC,OAAO,CACP,SAAQ,IAAI,CACV,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,UAAU,GAAG,SAAS,GAAG,gBAAgB,GAAG,kBAAkB,CAC/D;IACD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,WAAW,CACzB,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACtC,OAAO,GAAG,OAAO,EACjB,IAAI,EAAE,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC;4BAoDN,WAAW,EAAE,YAAY,OAAO;EAiCjE"}
1
+ {"version":3,"file":"create-agent.d.ts","sourceRoot":"","sources":["../../src/chat/create-agent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,OAAO,EACL,KAAK,aAAa,EAEnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAO1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGzD,MAAM,WAAW,kBAAkB;IACjC,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IACxC,SAAS,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,OAAO,CAAC,EAAE,kBAAkB,CAAC;IAC7B,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAED,MAAM,WAAW,KAAK;IACpB,WAAW,EAAE,CACX,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,CAAC,EAAE,mBAAmB,KAC1B,OAAO,CAAC,QAAQ,CAAC,CAAC;CACxB;AA6FD,wBAAgB,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,KAAK,CA6ExD"}
@@ -2,69 +2,136 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createAgent = createAgent;
4
4
  const node_path_1 = require("node:path");
5
+ const memory_1 = require("@kortyx/memory");
6
+ const providers_1 = require("@kortyx/providers");
5
7
  const runtime_1 = require("@kortyx/runtime");
8
+ const zod_1 = require("zod");
6
9
  const process_chat_1 = require("./process-chat");
10
+ const agentProcessOptionsSchema = zod_1.z
11
+ .object({
12
+ sessionId: zod_1.z.string().optional(),
13
+ workflowId: zod_1.z.string().optional(),
14
+ workflow: zod_1.z.string().optional(),
15
+ })
16
+ .strict();
17
+ const createAgentArgsBaseSchema = zod_1.z
18
+ .object({
19
+ getProvider: zod_1.z.unknown().optional(),
20
+ workflows: zod_1.z.array(zod_1.z.unknown()).optional(),
21
+ workflowsDir: zod_1.z.string().optional(),
22
+ workflowRegistry: zod_1.z.unknown().optional(),
23
+ fallbackWorkflowId: zod_1.z.string().optional(),
24
+ defaultWorkflowId: zod_1.z.string().optional(),
25
+ frameworkAdapter: zod_1.z.unknown().optional(),
26
+ session: zod_1.z
27
+ .object({
28
+ id: zod_1.z.string().optional(),
29
+ })
30
+ .strict()
31
+ .optional(),
32
+ memory: zod_1.z
33
+ .object({
34
+ enabled: zod_1.z.boolean().optional(),
35
+ namespace: zod_1.z.string().optional(),
36
+ ttlMs: zod_1.z.number().finite().positive().optional(),
37
+ })
38
+ .strict()
39
+ .optional(),
40
+ })
41
+ .strict();
42
+ const createAgentArgsSchema = createAgentArgsBaseSchema.superRefine((value, ctx) => {
43
+ if (value.getProvider !== undefined &&
44
+ typeof value.getProvider !== "function") {
45
+ ctx.addIssue({
46
+ code: zod_1.z.ZodIssueCode.custom,
47
+ message: "Expected `args.getProvider` to be a function.",
48
+ path: ["getProvider"],
49
+ });
50
+ }
51
+ const workflowSources = [
52
+ value.workflows !== undefined,
53
+ value.workflowsDir !== undefined,
54
+ value.workflowRegistry !== undefined,
55
+ ].filter(Boolean).length;
56
+ if (workflowSources > 1) {
57
+ ctx.addIssue({
58
+ code: zod_1.z.ZodIssueCode.custom,
59
+ message: "Use only one workflow source: `workflows`, `workflowsDir`, or `workflowRegistry`.",
60
+ });
61
+ }
62
+ });
63
+ const parseSchema = (schema, value) => {
64
+ const parsed = schema.safeParse(value);
65
+ if (parsed.success)
66
+ return parsed.data;
67
+ const firstIssue = parsed.error.issues[0];
68
+ throw new Error(firstIssue?.message ?? "Invalid configuration.");
69
+ };
70
+ const parseCreateAgentArgs = (value) => parseSchema(createAgentArgsSchema, value);
71
+ const parseAgentProcessOptions = (value) => {
72
+ if (value === undefined)
73
+ return undefined;
74
+ return parseSchema(agentProcessOptionsSchema, value);
75
+ };
76
+ const resolveMemoryAdapter = (memory) => {
77
+ if (memory?.enabled === false)
78
+ return undefined;
79
+ return (0, memory_1.createInMemoryAdapter)({
80
+ namespace: memory?.namespace ?? "kortyx-agent",
81
+ ttlMs: memory?.ttlMs ?? 1000 * 60 * 60,
82
+ });
83
+ };
7
84
  function createAgent(args) {
8
- const { workflowsDir, workflowRegistry, selectWorkflow, fallbackWorkflowId, config, configPath, defaultWorkflowId, frameworkAdapter, ...baseArgs } = args;
9
- const resolvedDefaultWorkflowId = defaultWorkflowId ?? config?.fallbackWorkflowId ?? fallbackWorkflowId;
85
+ const parsedArgs = parseCreateAgentArgs(args);
86
+ const { getProvider, workflows, workflowsDir, workflowRegistry, fallbackWorkflowId, defaultWorkflowId, frameworkAdapter, session, memory, } = parsedArgs;
87
+ const resolvedDefaultWorkflowId = defaultWorkflowId ?? fallbackWorkflowId;
10
88
  const resolvedFrameworkAdapter = frameworkAdapter ?? (0, runtime_1.createFrameworkAdapterFromEnv)();
89
+ const defaultSessionId = session?.id ?? "anonymous-session";
90
+ const memoryAdapter = resolveMemoryAdapter(memory);
91
+ const resolvedGetProvider = getProvider ?? providers_1.getProvider;
11
92
  const resolvedCwd = process.cwd();
12
93
  const registryPromise = (async () => {
13
94
  if (workflowRegistry)
14
95
  return workflowRegistry;
96
+ if (workflows) {
97
+ return (0, runtime_1.createInMemoryWorkflowRegistry)(workflows, {
98
+ fallbackId: fallbackWorkflowId ?? "general-chat",
99
+ });
100
+ }
15
101
  if (workflowsDir) {
16
102
  return (0, runtime_1.createFileWorkflowRegistry)({
17
103
  workflowsDir,
18
104
  fallbackId: fallbackWorkflowId ?? "general-chat",
19
105
  });
20
106
  }
21
- const loadConfigArgs = {
22
- cwd: resolvedCwd,
23
- ...(configPath ? { configPath } : {}),
24
- };
25
- const loadedConfig = config ?? (await (0, runtime_1.loadKortyxConfig)(loadConfigArgs));
26
- const resolvedWorkflowsDir = loadedConfig?.workflowsDir ?? (0, node_path_1.resolve)(resolvedCwd, "src", "workflows");
27
- const registryOptions = {
107
+ const resolvedWorkflowsDir = (0, node_path_1.resolve)(resolvedCwd, "src", "workflows");
108
+ return (0, runtime_1.createFileWorkflowRegistry)({
28
109
  workflowsDir: resolvedWorkflowsDir,
29
- fallbackId: loadedConfig?.fallbackWorkflowId ??
30
- fallbackWorkflowId ??
31
- "general-chat",
32
- ...(loadedConfig?.registry?.cache !== undefined
33
- ? { cache: loadedConfig.registry.cache }
34
- : {}),
35
- ...(loadedConfig?.registry?.extensions
36
- ? { extensions: loadedConfig.registry.extensions }
37
- : {}),
38
- };
39
- return (0, runtime_1.createFileWorkflowRegistry)(registryOptions);
110
+ fallbackId: fallbackWorkflowId ?? "general-chat",
111
+ });
40
112
  })();
41
113
  return {
42
114
  processChat: async (messages, options) => {
43
- if (selectWorkflow) {
44
- return (0, process_chat_1.processChat)({
45
- ...baseArgs,
46
- ...(resolvedDefaultWorkflowId
47
- ? { defaultWorkflowId: resolvedDefaultWorkflowId }
48
- : {}),
49
- messages,
50
- options,
51
- selectWorkflow,
52
- frameworkAdapter: resolvedFrameworkAdapter,
53
- });
54
- }
115
+ const parsedOptions = parseAgentProcessOptions(options);
55
116
  const registry = await registryPromise;
56
117
  if (!registry) {
57
- throw new Error("createAgent requires workflowsDir, workflowRegistry, or selectWorkflow.");
118
+ throw new Error("createAgent requires workflows, workflowsDir, or workflowRegistry.");
58
119
  }
59
120
  return (0, process_chat_1.processChat)({
60
- ...baseArgs,
61
121
  ...(resolvedDefaultWorkflowId
62
122
  ? { defaultWorkflowId: resolvedDefaultWorkflowId }
63
123
  : {}),
64
124
  messages,
65
- options,
125
+ options: parsedOptions,
66
126
  workflowRegistry: registry,
67
127
  frameworkAdapter: resolvedFrameworkAdapter,
128
+ getProvider: resolvedGetProvider,
129
+ ...(memoryAdapter ? { memoryAdapter } : {}),
130
+ loadRuntimeConfig: (runtimeOptions) => ({
131
+ session: {
132
+ id: runtimeOptions?.sessionId ?? defaultSessionId,
133
+ },
134
+ }),
68
135
  });
69
136
  },
70
137
  };
@@ -4,23 +4,24 @@ import type { FrameworkAdapter, WorkflowRegistry } from "@kortyx/runtime";
4
4
  import type { ApplyResumeSelection } from "../interrupt/resume-handler";
5
5
  import type { SelectWorkflowFn } from "../orchestrator";
6
6
  import type { ChatMessage } from "../types/chat-message";
7
- type InitializeProvidersFn<Config> = (aiConfig: Config extends {
8
- ai: infer A;
9
- } ? A : unknown) => void;
10
- export interface ProcessChatArgs<Config extends Record<string, unknown>, Options> {
7
+ export interface RuntimeConfig {
8
+ session?: {
9
+ id?: string;
10
+ };
11
+ [key: string]: unknown;
12
+ }
13
+ export interface ProcessChatArgs<Options> {
11
14
  messages: ChatMessage[];
12
15
  options?: Options | undefined;
13
16
  sessionId?: string;
14
17
  defaultWorkflowId?: string;
15
- loadRuntimeConfig: (options?: Options) => Config | Promise<Config>;
18
+ loadRuntimeConfig: (options?: Options) => RuntimeConfig | Promise<RuntimeConfig>;
16
19
  selectWorkflow?: SelectWorkflowFn;
17
20
  workflowRegistry?: WorkflowRegistry;
18
21
  frameworkAdapter?: FrameworkAdapter;
19
22
  getProvider: GetProviderFn;
20
- initializeProviders?: InitializeProvidersFn<Config>;
21
23
  memoryAdapter?: MemoryAdapter;
22
24
  applyResumeSelection?: ApplyResumeSelection;
23
25
  }
24
- export declare function processChat<Config extends Record<string, unknown>, Options = unknown>({ messages, options, sessionId, defaultWorkflowId, loadRuntimeConfig, selectWorkflow, workflowRegistry, frameworkAdapter, getProvider, initializeProviders, memoryAdapter, applyResumeSelection, }: ProcessChatArgs<Config, Options>): Promise<Response>;
25
- export {};
26
+ export declare function processChat<Options = unknown>({ messages, options, sessionId, defaultWorkflowId, loadRuntimeConfig, selectWorkflow, workflowRegistry, frameworkAdapter, getProvider, memoryAdapter, applyResumeSelection, }: ProcessChatArgs<Options>): Promise<Response>;
26
27
  //# sourceMappingURL=process-chat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"process-chat.d.ts","sourceRoot":"","sources":["../../src/chat/process-chat.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAO1E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAKxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGzD,KAAK,qBAAqB,CAAC,MAAM,IAAI,CACnC,QAAQ,EAAE,MAAM,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,CAAC,GAAG,OAAO,KACnD,IAAI,CAAC;AAEV,MAAM,WAAW,eAAe,CAC9B,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACtC,OAAO;IAEP,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACnE,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,WAAW,EAAE,aAAa,CAAC;IAC3B,mBAAmB,CAAC,EAAE,qBAAqB,CAAC,MAAM,CAAC,CAAC;IACpD,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;CAC7C;AAED,wBAAsB,WAAW,CAC/B,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACtC,OAAO,GAAG,OAAO,EACjB,EACA,QAAQ,EACR,OAAO,EACP,SAAS,EACT,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,mBAAmB,EACnB,aAAa,EACb,oBAAoB,GACrB,EAAE,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAyGtD"}
1
+ {"version":3,"file":"process-chat.d.ts","sourceRoot":"","sources":["../../src/chat/process-chat.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAO1E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAKxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAGzD,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,eAAe,CAAC,OAAO;IACtC,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,CACjB,OAAO,CAAC,EAAE,OAAO,KACd,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IAC5C,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAClC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,WAAW,EAAE,aAAa,CAAC;IAC3B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;CAC7C;AAED,wBAAsB,WAAW,CAAC,OAAO,GAAG,OAAO,EAAE,EACnD,QAAQ,EACR,OAAO,EACP,SAAS,EACT,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,aAAa,EACb,oBAAoB,GACrB,EAAE,eAAe,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAqF9C"}
@@ -6,14 +6,8 @@ const stream_1 = require("@kortyx/stream");
6
6
  const resume_handler_1 = require("../interrupt/resume-handler");
7
7
  const orchestrator_1 = require("../orchestrator");
8
8
  const extract_latest_message_1 = require("../utils/extract-latest-message");
9
- async function processChat({ messages, options, sessionId, defaultWorkflowId, loadRuntimeConfig, selectWorkflow, workflowRegistry, frameworkAdapter, getProvider, initializeProviders, memoryAdapter, applyResumeSelection, }) {
9
+ async function processChat({ messages, options, sessionId, defaultWorkflowId, loadRuntimeConfig, selectWorkflow, workflowRegistry, frameworkAdapter, getProvider, memoryAdapter, applyResumeSelection, }) {
10
10
  const config = await loadRuntimeConfig(options);
11
- if (initializeProviders) {
12
- const ai = config.ai;
13
- initializeProviders((ai && typeof ai === "object" && !Array.isArray(ai)
14
- ? ai
15
- : {}));
16
- }
17
11
  const runtimeConfig = {
18
12
  ...config,
19
13
  getProvider,
package/dist/index.d.ts CHANGED
@@ -1,4 +1,8 @@
1
- export type { CreateAgentArgs } from "./chat/create-agent";
1
+ export type { ChatRequestBody } from "./adapters/http";
2
+ export { createChatRouteHandler, parseChatRequestBody, processChatRequestBody, } from "./adapters/http";
3
+ export type { StreamChatFromRouteArgs } from "./adapters/http-client";
4
+ export { streamChatFromRoute } from "./adapters/http-client";
5
+ export type { Agent, AgentMemoryConfig, AgentProcessOptions, AgentSessionConfig, CreateAgentArgs, } from "./chat/create-agent";
2
6
  export { createAgent } from "./chat/create-agent";
3
7
  export type { ProcessChatArgs } from "./chat/process-chat";
4
8
  export { processChat } from "./chat/process-chat";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,YAAY,EACV,oBAAoB,EACpB,UAAU,GACX,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,eAAe,EACf,sBAAsB,GACvB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EACV,iBAAiB,EACjB,eAAe,EACf,YAAY,EACZ,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,yBAAyB,EAAE,MAAM,wCAAwC,CAAC;AACnF,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,YAAY,EACV,KAAK,EACL,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,GAChB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,YAAY,EACV,oBAAoB,EACpB,UAAU,GACX,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,eAAe,EACf,sBAAsB,GACvB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EACV,iBAAiB,EACjB,eAAe,EACf,YAAY,EACZ,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,yBAAyB,EAAE,MAAM,wCAAwC,CAAC;AACnF,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.extractLatestUserMessage = exports.transformGraphStreamForUI = exports.orchestrateGraphStream = exports.tryPrepareResumeStream = exports.parseResumeMeta = exports.processChat = exports.createAgent = void 0;
3
+ exports.extractLatestUserMessage = exports.transformGraphStreamForUI = exports.orchestrateGraphStream = exports.tryPrepareResumeStream = exports.parseResumeMeta = exports.processChat = exports.createAgent = exports.streamChatFromRoute = exports.processChatRequestBody = exports.parseChatRequestBody = exports.createChatRouteHandler = void 0;
4
+ var http_1 = require("./adapters/http");
5
+ Object.defineProperty(exports, "createChatRouteHandler", { enumerable: true, get: function () { return http_1.createChatRouteHandler; } });
6
+ Object.defineProperty(exports, "parseChatRequestBody", { enumerable: true, get: function () { return http_1.parseChatRequestBody; } });
7
+ Object.defineProperty(exports, "processChatRequestBody", { enumerable: true, get: function () { return http_1.processChatRequestBody; } });
8
+ var http_client_1 = require("./adapters/http-client");
9
+ Object.defineProperty(exports, "streamChatFromRoute", { enumerable: true, get: function () { return http_client_1.streamChatFromRoute; } });
4
10
  var create_agent_1 = require("./chat/create-agent");
5
11
  Object.defineProperty(exports, "createAgent", { enumerable: true, get: function () { return create_agent_1.createAgent; } });
6
12
  var process_chat_1 = require("./chat/process-chat");
@@ -1 +1 @@
1
- {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../src/orchestrator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAc,MAAM,cAAc,CAAC;AAC/E,OAAO,EAEL,KAAK,gBAAgB,EAKtB,MAAM,iBAAiB,CAAC;AAKzB,MAAM,MAAM,gBAAgB,GAAG,CAC7B,UAAU,EAAE,MAAM,KACf,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAEjC,MAAM,MAAM,YAAY,GAAG,CACzB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,UAAU,KACd,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,YAAY,EAAE,CACZ,KAAK,EAAE,UAAU,EACjB,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,KACnE,aAAa,CAAC,OAAO,CAAC,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;CACvD;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,iBAAiB,CAAC;IACzB,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,cAAc,EAAE,gBAAgB,CAAC;IACjC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAQD,wBAAsB,sBAAsB,CAAC,EAC3C,SAAS,EACT,KAAK,EACL,KAAK,EACL,KAAK,EACL,MAAM,EACN,cAAc,EACd,gBAAgB,GACjB,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAkgBlD"}
1
+ {"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../src/orchestrator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAc,MAAM,cAAc,CAAC;AAC/E,OAAO,EAEL,KAAK,gBAAgB,EAKtB,MAAM,iBAAiB,CAAC;AAKzB,MAAM,MAAM,gBAAgB,GAAG,CAC7B,UAAU,EAAE,MAAM,KACf,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAEjC,MAAM,MAAM,YAAY,GAAG,CACzB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,UAAU,KACd,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,YAAY,EAAE,CACZ,KAAK,EAAE,UAAU,EACjB,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,KACnE,aAAa,CAAC,OAAO,CAAC,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;CACvD;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,iBAAiB,CAAC;IACzB,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,cAAc,EAAE,gBAAgB,CAAC;IACjC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;CACrC;AAMD,wBAAsB,sBAAsB,CAAC,EAC3C,SAAS,EACT,KAAK,EACL,KAAK,EACL,KAAK,EACL,MAAM,EACN,cAAc,EACd,gBAAgB,GACjB,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAgZlD"}
@@ -10,6 +10,7 @@ async function orchestrateGraphStream({ sessionId, runId, graph, state, config,
10
10
  let currentGraph = graph;
11
11
  let currentState = state;
12
12
  let finished = false;
13
+ const debugEnabled = Boolean(config?.features?.tracing);
13
14
  const namespacesUsed = new Set();
14
15
  try {
15
16
  const sid = config?.session?.id;
@@ -22,7 +23,6 @@ async function orchestrateGraphStream({ sessionId, runId, graph, state, config,
22
23
  to: null,
23
24
  payload: {},
24
25
  };
25
- const streamedNodes = new Set();
26
26
  let lastStatusMsg = "";
27
27
  let lastStatusAt = 0;
28
28
  let pendingRecordToken = null;
@@ -30,6 +30,70 @@ async function orchestrateGraphStream({ sessionId, runId, graph, state, config,
30
30
  let wroteHumanInput = false;
31
31
  const pendingStore = frameworkAdapter?.pendingRequests;
32
32
  const pendingTtlMs = frameworkAdapter?.ttlMs ?? 15 * 60 * 1000;
33
+ const persistAndEmitInterrupt = async (payload) => {
34
+ if (activeIsResume || wroteHumanInput)
35
+ return;
36
+ const token = (0, runtime_1.makeResumeToken)();
37
+ const requestId = (0, runtime_1.makeRequestId)("human");
38
+ pendingRecordToken = token;
39
+ const input = payload.input ?? {};
40
+ const optionsList = Array.isArray(input.options) ? input.options : [];
41
+ const kind = input.kind || (input.multiple ? "multi-choice" : "choice");
42
+ const isText = kind === "text";
43
+ const record = {
44
+ token,
45
+ requestId,
46
+ sessionId,
47
+ runId,
48
+ workflow: payload.workflow || currentState.currentWorkflow,
49
+ node: payload.node || "",
50
+ state: { ...currentState, awaitingHumanInput: true },
51
+ schema: isText
52
+ ? {
53
+ kind: kind,
54
+ multiple: Boolean(input.multiple),
55
+ ...(input.question ? { question: input.question } : {}),
56
+ }
57
+ : {
58
+ kind: kind,
59
+ multiple: Boolean(input.multiple),
60
+ question: String(input.question || "Please choose an option."),
61
+ },
62
+ options: optionsList.map((option) => ({
63
+ id: String(option.id),
64
+ label: String(option.label),
65
+ description: typeof option.description === "string"
66
+ ? option.description
67
+ : undefined,
68
+ value: option.value,
69
+ })),
70
+ createdAt: Date.now(),
71
+ ttlMs: pendingTtlMs,
72
+ };
73
+ if (pendingStore) {
74
+ pendingStore.save(record).catch((error) => {
75
+ console.error("[orchestrator] failed to save pending request", error);
76
+ });
77
+ }
78
+ out.write({
79
+ type: "interrupt",
80
+ requestId: record.requestId,
81
+ resumeToken: record.token,
82
+ workflow: record.workflow,
83
+ node: record.node,
84
+ input: {
85
+ kind: record.schema.kind,
86
+ multiple: record.schema.multiple,
87
+ question: record.schema.question,
88
+ options: record.options.map((option) => ({
89
+ id: option.id,
90
+ label: option.label,
91
+ description: option.description,
92
+ })),
93
+ },
94
+ });
95
+ wroteHumanInput = true;
96
+ };
33
97
  const forwardEmit = (event, payload) => {
34
98
  if (event === "error") {
35
99
  const msg = String(payload?.message ?? "Unexpected error");
@@ -40,6 +104,8 @@ async function orchestrateGraphStream({ sessionId, runId, graph, state, config,
40
104
  return;
41
105
  }
42
106
  if (event === "status") {
107
+ if (!debugEnabled)
108
+ return;
43
109
  const msg = String(payload?.message ?? "");
44
110
  const now = Date.now();
45
111
  if (msg && msg === lastStatusMsg && now - lastStatusAt < 250)
@@ -54,7 +120,6 @@ async function orchestrateGraphStream({ sessionId, runId, graph, state, config,
54
120
  if (!node)
55
121
  return;
56
122
  out.write({ type: "text-start", node });
57
- streamedNodes.add(node);
58
123
  return;
59
124
  }
60
125
  if (event === "text-delta") {
@@ -63,7 +128,6 @@ async function orchestrateGraphStream({ sessionId, runId, graph, state, config,
63
128
  if (!node || !delta)
64
129
  return;
65
130
  out.write({ type: "text-delta", delta, node });
66
- streamedNodes.add(node);
67
131
  return;
68
132
  }
69
133
  if (event === "text-end") {
@@ -100,81 +164,15 @@ async function orchestrateGraphStream({ sessionId, runId, graph, state, config,
100
164
  return;
101
165
  }
102
166
  if (event === "interrupt") {
103
- if (activeIsResume)
104
- return;
105
- try {
106
- const p = payload;
107
- console.log(`[orchestrator] interrupt node=${p?.node} workflow=${p?.workflow} options=${Array.isArray(p?.input?.options) ? p.input.options.length : 0}`);
108
- }
109
- catch { }
110
167
  const p = payload;
111
168
  const local = {
112
169
  node: p?.node,
113
170
  workflow: p?.workflow,
114
171
  input: p?.input,
115
172
  };
116
- const token = (0, runtime_1.makeResumeToken)();
117
- const requestId = (0, runtime_1.makeRequestId)("human");
118
- pendingRecordToken = token;
119
- const options = Array.isArray(local.input?.options)
120
- ? local.input.options
121
- : [];
122
- const kind = local.input?.kind ||
123
- (local.input?.multiple ? "multi-choice" : "choice");
124
- const isText = kind === "text";
125
- const record = {
126
- token,
127
- requestId,
128
- sessionId: sessionId,
129
- runId,
130
- workflow: local.workflow || currentState.currentWorkflow,
131
- node: local.node || "",
132
- state: { ...currentState, awaitingHumanInput: true },
133
- schema: isText
134
- ? {
135
- kind: kind,
136
- multiple: Boolean(local.input?.multiple),
137
- ...(local.input?.question
138
- ? { question: local.input.question }
139
- : {}),
140
- }
141
- : {
142
- kind: kind,
143
- multiple: Boolean(local.input?.multiple),
144
- question: String(local.input?.question || "Please choose an option."),
145
- },
146
- options: options.map((o) => ({
147
- id: String(o.id),
148
- label: String(o.label),
149
- description: typeof o.description === "string" ? o.description : undefined,
150
- value: o.value,
151
- })),
152
- createdAt: Date.now(),
153
- ttlMs: pendingTtlMs,
154
- };
155
- if (pendingStore) {
156
- pendingStore.save(record).catch((e) => {
157
- console.error("[orchestrator] failed to save pending request", e);
158
- });
159
- }
160
- out.write({
161
- type: "interrupt",
162
- requestId: record.requestId,
163
- resumeToken: record.token,
164
- workflow: record.workflow,
165
- node: record.node,
166
- input: {
167
- kind: record.schema.kind,
168
- multiple: record.schema.multiple,
169
- question: record.schema.question,
170
- options: record.options.map((o) => ({
171
- id: o.id,
172
- label: o.label,
173
- description: o.description,
174
- })),
175
- },
173
+ void persistAndEmitInterrupt(local).catch((error) => {
174
+ console.error("[orchestrator] failed to emit interrupt", error);
176
175
  });
177
- wroteHumanInput = true;
178
176
  return;
179
177
  }
180
178
  };
@@ -188,10 +186,12 @@ async function orchestrateGraphStream({ sessionId, runId, graph, state, config,
188
186
  "anonymous-session";
189
187
  const checkpointNs = String(currentState.currentWorkflow || "default");
190
188
  namespacesUsed.add(checkpointNs);
191
- out.write({
192
- type: "status",
193
- message: `🧵 thread_id=${threadId} run_id=${runId} workflow=${currentState.currentWorkflow}`,
194
- });
189
+ if (debugEnabled) {
190
+ out.write({
191
+ type: "status",
192
+ message: `🧵 thread_id=${threadId} run_id=${runId} workflow=${currentState.currentWorkflow}`,
193
+ });
194
+ }
195
195
  const isResume = Boolean(currentGraph.config?.resume);
196
196
  activeIsResume = isResume;
197
197
  const resumeUpdate = currentGraph.config?.resumeUpdate;
@@ -210,119 +210,20 @@ async function orchestrateGraphStream({ sessionId, runId, graph, state, config,
210
210
  checkpoint_ns: checkpointNs,
211
211
  },
212
212
  });
213
- try {
213
+ if (debugEnabled) {
214
214
  out.write({
215
215
  type: "status",
216
216
  message: `▶️ streamEvents invoke: resume=${Boolean(currentGraph.config?.resume)} thread_id=${threadId} run_id=${runId} ns=${String(currentState.currentWorkflow || "default")}`,
217
217
  });
218
218
  }
219
- catch { }
220
219
  const uiStream = (0, transform_graph_stream_for_ui_1.transformGraphStreamForUI)(runtimeStream, {
221
- debug: Boolean(config?.features?.tracing),
220
+ debug: debugEnabled,
221
+ emitStatus: debugEnabled,
222
222
  });
223
- let loopTransitionTo = null;
224
- let loopTransitionPayload = {};
225
223
  for await (const chunk of uiStream) {
226
224
  if (finished)
227
225
  break;
228
- const node = chunk.node;
229
- if (chunk.type === "interrupt" &&
230
- (!chunk.resumeToken || !chunk.requestId)) {
231
- if (wroteHumanInput) {
232
- continue;
233
- }
234
- const hi = chunk;
235
- const token = (0, runtime_1.makeResumeToken)();
236
- const requestId = (0, runtime_1.makeRequestId)("human");
237
- pendingRecordToken = token;
238
- const options = Array.isArray(hi.input?.options)
239
- ? hi.input.options
240
- : [];
241
- const kind = hi.input?.kind || (hi.input?.multiple ? "multi-choice" : "choice");
242
- const isText = kind === "text";
243
- const record = {
244
- token,
245
- requestId,
246
- sessionId: sessionId,
247
- runId,
248
- workflow: currentState.currentWorkflow,
249
- node: node || "",
250
- state: {
251
- ...currentState,
252
- awaitingHumanInput: true,
253
- },
254
- schema: isText
255
- ? {
256
- kind: kind,
257
- multiple: Boolean(hi.input?.multiple),
258
- ...(hi.input?.question
259
- ? { question: hi.input.question }
260
- : {}),
261
- }
262
- : {
263
- kind: kind,
264
- multiple: Boolean(hi.input?.multiple),
265
- question: String(hi.input?.question || "Please choose an option."),
266
- },
267
- options: options.map((o) => ({
268
- id: String(o.id),
269
- label: String(o.label),
270
- description: typeof o.description === "string" ? o.description : undefined,
271
- value: o.value,
272
- })),
273
- createdAt: Date.now(),
274
- ttlMs: pendingTtlMs,
275
- };
276
- if (pendingStore) {
277
- await pendingStore.save(record);
278
- }
279
- out.write({
280
- type: "interrupt",
281
- requestId,
282
- resumeToken: token,
283
- workflow: record.workflow,
284
- node: record.node,
285
- input: {
286
- kind: record.schema.kind,
287
- multiple: record.schema.multiple,
288
- question: record.schema.question,
289
- options: record.options.map((o) => ({
290
- id: o.id,
291
- label: o.label,
292
- description: o.description,
293
- })),
294
- },
295
- });
296
- wroteHumanInput = true;
297
- continue;
298
- }
299
- if (chunk.type === "text-delta") {
300
- if (typeof chunk.delta === "string" && chunk.delta.length > 60) {
301
- const text = chunk.delta;
302
- for (let i = 0; i < text.length; i += 60) {
303
- out.write({
304
- type: "text-delta",
305
- delta: text.slice(i, i + 60),
306
- node,
307
- });
308
- }
309
- if (node)
310
- streamedNodes.add(node);
311
- }
312
- else {
313
- out.write(chunk);
314
- if (node)
315
- streamedNodes.add(node);
316
- }
317
- }
318
- else {
319
- out.write(chunk);
320
- }
321
- if (chunk.type === "transition") {
322
- loopTransitionTo = String(chunk.transitionTo || "");
323
- loopTransitionPayload = chunk.payload ?? {};
324
- break;
325
- }
226
+ out.write(chunk);
326
227
  if (chunk.type === "done") {
327
228
  workflowFinalState = chunk.data ?? null;
328
229
  break;
@@ -330,10 +231,8 @@ async function orchestrateGraphStream({ sessionId, runId, graph, state, config,
330
231
  }
331
232
  if (finished)
332
233
  return;
333
- const transitionTo = loopTransitionTo || pending.to;
334
- const transitionPayload = Object.keys(loopTransitionPayload).length
335
- ? loopTransitionPayload
336
- : pending.payload;
234
+ const transitionTo = pending.to;
235
+ const transitionPayload = pending.payload;
337
236
  pending.to = null;
338
237
  pending.payload = {};
339
238
  if (transitionTo) {
@@ -363,9 +262,10 @@ async function orchestrateGraphStream({ sessionId, runId, graph, state, config,
363
262
  }
364
263
  catch (err) {
365
264
  out.write({
366
- type: "status",
367
- message: `⚠️ Transition failed to '${transitionTo}': ${err instanceof Error ? err.message : String(err)}`,
265
+ type: "error",
266
+ message: `Transition failed to '${transitionTo}': ${err instanceof Error ? err.message : String(err)}`,
368
267
  });
268
+ out.write({ type: "done" });
369
269
  out.end();
370
270
  return;
371
271
  }
@@ -410,7 +310,11 @@ async function orchestrateGraphStream({ sessionId, runId, graph, state, config,
410
310
  }
411
311
  })().catch((err) => {
412
312
  console.error("[error:orchestrateGraphStream]", err);
413
- out.write({ type: "status", message: `Error: ${err.message}` });
313
+ out.write({
314
+ type: "error",
315
+ message: err instanceof Error ? err.message : String(err),
316
+ });
317
+ out.write({ type: "done" });
414
318
  out.end();
415
319
  });
416
320
  return out;
@@ -2,8 +2,7 @@ import type { StreamChunk } from "@kortyx/stream";
2
2
  import type { StreamEvent } from "@langchain/core/tracers/log_stream";
3
3
  interface TransformOptions {
4
4
  debug?: boolean;
5
- visibleNodes?: string[];
6
- forwardModelStream?: boolean;
5
+ emitStatus?: boolean;
7
6
  }
8
7
  export declare function transformGraphStreamForUI(stream: AsyncIterable<StreamEvent>, options?: TransformOptions): AsyncGenerator<StreamChunk>;
9
8
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"transform-graph-stream-for-ui.d.ts","sourceRoot":"","sources":["../../src/stream/transform-graph-stream-for-ui.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AAEtE,UAAU,gBAAgB;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AASD,wBAAuB,yBAAyB,CAC9C,MAAM,EAAE,aAAa,CAAC,WAAW,CAAC,EAClC,OAAO,GAAE,gBAAqB,GAC7B,cAAc,CAAC,WAAW,CAAC,CAgO7B"}
1
+ {"version":3,"file":"transform-graph-stream-for-ui.d.ts","sourceRoot":"","sources":["../../src/stream/transform-graph-stream-for-ui.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AAEtE,UAAU,gBAAgB;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAQD,wBAAuB,yBAAyB,CAC9C,MAAM,EAAE,aAAa,CAAC,WAAW,CAAC,EAClC,OAAO,GAAE,gBAAqB,GAC7B,cAAc,CAAC,WAAW,CAAC,CA6D7B"}
@@ -1,32 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.transformGraphStreamForUI = transformGraphStreamForUI;
4
- const utils_1 = require("@kortyx/utils");
5
4
  async function* transformGraphStreamForUI(stream, options = {}) {
6
- const { debug = false, forwardModelStream = false } = options;
7
- let currentNode = null;
8
- const streamedTextByNode = new Map();
5
+ const { debug = false, emitStatus = debug } = options;
9
6
  const startedNodes = new Set();
10
7
  const endedNodes = new Set();
11
- let sawInterrupt = false;
12
- function findChoiceSchema(obj) {
13
- try {
14
- if (!obj || typeof obj !== "object")
15
- return null;
16
- if (typeof obj.kind === "string" &&
17
- (obj.kind === "choice" || obj.kind === "multi-choice") &&
18
- Array.isArray(obj.options)) {
19
- return obj;
20
- }
21
- for (const v of Object.values(obj)) {
22
- const inner = findChoiceSchema(v);
23
- if (inner)
24
- return inner;
25
- }
26
- }
27
- catch { }
28
- return null;
29
- }
30
8
  for await (const event of stream) {
31
9
  const { event: type, name, data } = event ?? {};
32
10
  if (debug)
@@ -34,7 +12,6 @@ async function* transformGraphStreamForUI(stream, options = {}) {
34
12
  switch (type) {
35
13
  case "on_chain_start":
36
14
  if (name && !name.startsWith("ChannelWrite")) {
37
- currentNode = name;
38
15
  if (name === "__start__" || name === "__end__")
39
16
  break;
40
17
  if (startedNodes.has(name))
@@ -42,67 +19,8 @@ async function* transformGraphStreamForUI(stream, options = {}) {
42
19
  startedNodes.add(name);
43
20
  if (debug)
44
21
  console.log(`[debug:start] node=${name}`);
45
- yield { type: "status", message: `Processing node: ${name}` };
46
- }
47
- break;
48
- case "on_graph_interrupt": {
49
- sawInterrupt = true;
50
- if (debug)
51
- console.log(`[debug:on_graph_interrupt]`, JSON.stringify({ name, data }, null, 2));
52
- const where = (typeof name === "string" && name) ||
53
- data?.node ||
54
- "unknown";
55
- const schema = findChoiceSchema(data);
56
- if (schema) {
57
- const isText = schema.kind === "text";
58
- const options = Array.isArray(schema.options)
59
- ? schema.options
60
- .map((o) => ({
61
- id: String(o.id ?? ""),
62
- label: String(o.label ?? ""),
63
- ...(o.description
64
- ? { description: String(o.description) }
65
- : {}),
66
- }))
67
- .filter((o) => o.id && o.label)
68
- : [];
69
- yield {
70
- type: "interrupt",
71
- requestId: "",
72
- resumeToken: "",
73
- node: typeof where === "string" ? where : undefined,
74
- input: {
75
- kind: schema.kind,
76
- multiple: Boolean(schema.multiple),
77
- ...(isText
78
- ? { question: schema.question }
79
- : {
80
- question: typeof schema.question === "string"
81
- ? schema.question
82
- : "Please choose",
83
- }),
84
- ...(options.length > 0 ? { options } : {}),
85
- },
86
- };
87
- }
88
- else {
89
- yield { type: "status", message: `⏸️ Interrupted at: ${where}` };
90
- }
91
- break;
92
- }
93
- case "on_chat_model_stream":
94
- if (forwardModelStream && data?.chunk?.content) {
95
- if (currentNode && !streamedTextByNode.get(currentNode)) {
96
- yield { type: "text-start", node: currentNode };
97
- streamedTextByNode.set(currentNode, true);
98
- }
99
- const delta = (0, utils_1.contentToText)(data.chunk.content);
100
- if (delta) {
101
- yield {
102
- type: "text-delta",
103
- delta,
104
- node: currentNode ?? "ai",
105
- };
22
+ if (emitStatus) {
23
+ yield { type: "status", message: `Processing node: ${name}` };
106
24
  }
107
25
  }
108
26
  break;
@@ -113,79 +31,27 @@ async function* transformGraphStreamForUI(stream, options = {}) {
113
31
  console.log(`[debug:on_chain_end:${nodeName}] output=`, JSON.stringify(output, null, 2));
114
32
  if (!output || nodeName?.startsWith("ChannelWrite"))
115
33
  break;
116
- if (nodeName && streamedTextByNode.get(nodeName)) {
117
- yield { type: "text-end", node: nodeName };
118
- streamedTextByNode.delete(nodeName);
119
- }
120
34
  if (nodeName !== "__start__" && nodeName !== "__end__") {
121
35
  if (!endedNodes.has(nodeName)) {
122
- yield { type: "status", message: `✅ Completed node: ${nodeName}` };
36
+ if (emitStatus) {
37
+ yield {
38
+ type: "status",
39
+ message: `✅ Completed node: ${nodeName}`,
40
+ };
41
+ }
123
42
  endedNodes.add(nodeName);
124
43
  }
125
44
  }
126
- currentNode = null;
127
45
  break;
128
46
  }
129
47
  case "on_graph_end": {
130
48
  if (debug)
131
49
  console.log(`[debug:on_graph_end]`, JSON.stringify(data, null, 2));
132
- const out = data?.output ?? null;
133
- const interrupts = out && out.__interrupt__;
134
- if (interrupts && Array.isArray(interrupts) && interrupts.length > 0) {
135
- const first = interrupts[0];
136
- const val = first?.value ?? first;
137
- const schema = findChoiceSchema(val);
138
- if (schema && Array.isArray(schema.options)) {
139
- const isText = schema.kind === "text";
140
- const options = Array.isArray(schema.options)
141
- ? schema.options
142
- .map((o) => ({
143
- id: String(o.id ?? ""),
144
- label: String(o.label ?? ""),
145
- ...(o.description
146
- ? { description: String(o.description) }
147
- : {}),
148
- }))
149
- .filter((o) => o.id && o.label)
150
- : [];
151
- yield {
152
- type: "interrupt",
153
- requestId: "",
154
- resumeToken: "",
155
- input: {
156
- kind: schema.kind,
157
- multiple: Boolean(schema.multiple),
158
- ...(isText
159
- ? { question: schema.question }
160
- : {
161
- question: typeof schema.question === "string"
162
- ? schema.question
163
- : "Please choose",
164
- }),
165
- ...(options.length > 0 ? { options } : {}),
166
- },
167
- };
168
- }
169
- else {
170
- yield { type: "status", message: "⏸️ Interrupt received" };
171
- }
172
- }
173
- if (sawInterrupt && debug)
174
- yield { type: "status", message: "🔚 Graph ended after interrupt" };
175
- yield { type: "done", data: out };
50
+ yield { type: "done", data: data?.output ?? null };
176
51
  break;
177
52
  }
178
53
  default:
179
- if (typeof type === "string" && type.includes("interrupt")) {
180
- sawInterrupt = true;
181
- if (debug)
182
- console.log(`[debug:interrupt_like]`, JSON.stringify({ type, name, data }, null, 2));
183
- yield {
184
- type: "status",
185
- message: `⏸️ Interrupt event: ${type} ${name ?? ""}`.trim(),
186
- };
187
- }
188
- else if (debug) {
54
+ if (debug) {
189
55
  console.warn(`[debug:unknown_event]`, type);
190
56
  }
191
57
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kortyx/agent",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "description": "Agent composition utilities for Kortyx.",
6
6
  "keywords": [
@@ -23,6 +23,10 @@
23
23
  ".": {
24
24
  "types": "./dist/index.d.ts",
25
25
  "default": "./dist/index.js"
26
+ },
27
+ "./browser": {
28
+ "types": "./dist/browser.d.ts",
29
+ "default": "./dist/browser.js"
26
30
  }
27
31
  },
28
32
  "files": [
@@ -46,7 +50,8 @@
46
50
  "@kortyx/stream": "workspace:*",
47
51
  "@kortyx/utils": "workspace:*",
48
52
  "@langchain/core": "^1.0.1",
49
- "@langchain/langgraph": "^1.0.1"
53
+ "@langchain/langgraph": "^1.0.1",
54
+ "zod": "^3.23.8"
50
55
  },
51
56
  "devDependencies": {
52
57
  "turbo": "^2.5.4",