@fragno-dev/chatno 0.0.5

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.
@@ -0,0 +1,23 @@
1
+ $ tsdown
2
+ ℹ tsdown v0.15.5 powered by rolldown v1.0.0-beta.40
3
+ rollup
4
+ ℹ Using tsdown config: /home/runner/work/fragno/fragno/packages/chatno/tsdown.config.ts
5
+ ℹ entry: src/index.ts
6
+ ℹ tsconfig: tsconfig.json
7
+ ℹ entry: src/index.ts
8
+ ℹ tsconfig: tsconfig.json
9
+ ℹ Build start
10
+ ℹ dist/browser/index.js  3.62 kB │ gzip: 1.23 kB
11
+ ℹ dist/browser/index.js.map 12.00 kB │ gzip: 3.58 kB
12
+ ℹ dist/browser/index.d.ts.map  0.40 kB │ gzip: 0.23 kB
13
+ ℹ dist/browser/index.d.ts  2.83 kB │ gzip: 0.89 kB
14
+ ℹ 4 files, total: 18.84 kB
15
+ ✔ Build complete in 15780ms
16
+ ℹ dist/node/index.js 2.42 kB │ gzip: 1.06 kB
17
+ ℹ dist/node/server/chatno-api.js.map 6.89 kB │ gzip: 2.02 kB
18
+ ℹ dist/node/index.js.map 4.61 kB │ gzip: 1.80 kB
19
+ ℹ dist/node/server/chatno-api.js 3.21 kB │ gzip: 1.06 kB
20
+ ℹ dist/node/index.d.ts.map 0.40 kB │ gzip: 0.23 kB
21
+ ℹ dist/node/index.d.ts 2.83 kB │ gzip: 0.89 kB
22
+ ℹ 6 files, total: 20.35 kB
23
+ ✔ Build complete in 15866ms
@@ -0,0 +1 @@
1
+ $ tsc --noEmit
package/CHANGELOG.md ADDED
@@ -0,0 +1,29 @@
1
+ # @fragno-dev/chatno
2
+
3
+ ## 0.0.5
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [b9450b1]
8
+ - @fragno-dev/core@0.0.5
9
+
10
+ ## 0.0.4
11
+
12
+ ### Patch Changes
13
+
14
+ - Updated dependencies [1dae6f9]
15
+ - @fragno-dev/core@0.0.4
16
+
17
+ ## 0.0.3
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies [604caff]
22
+ - @fragno-dev/core@0.0.3
23
+
24
+ ## 0.0.2
25
+
26
+ ### Patch Changes
27
+
28
+ - Updated dependencies [2052919]
29
+ - @fragno-dev/core@0.0.2
@@ -0,0 +1,73 @@
1
+ import * as _fragno_dev_core0 from "@fragno-dev/core";
2
+ import { FragnoPublicClientConfig, FragnoPublicConfig } from "@fragno-dev/core";
3
+ import { z } from "zod";
4
+ import * as _fragno_dev_core_client0 from "@fragno-dev/core/client";
5
+ import OpenAI from "openai";
6
+ import * as nanostores0 from "nanostores";
7
+ import { FragnoRouteConfig } from "@fragno-dev/core/api";
8
+
9
+ //#region src/index.d.ts
10
+ interface ChatnoServerConfig {
11
+ openaiApiKey: string;
12
+ model?: "gpt-5-mini" | "4o-mini" | "gpt-5-nano";
13
+ systemPrompt?: string;
14
+ }
15
+ declare function createChatno(chatnoConfig: ChatnoServerConfig, fragnoConfig?: FragnoPublicConfig): _fragno_dev_core0.FragnoInstantiatedFragment<[_fragno_dev_core0.FragnoRouteConfig<"POST", "/chat/stream", z.ZodObject<{
16
+ messages: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
17
+ type: z.ZodLiteral<"chat">;
18
+ id: z.ZodString;
19
+ role: z.ZodEnum<{
20
+ user: "user";
21
+ assistant: "assistant";
22
+ }>;
23
+ content: z.ZodString;
24
+ }, z.core.$strip>, z.ZodObject<{
25
+ type: z.ZodLiteral<"functionCall">;
26
+ id: z.ZodString;
27
+ functionCallId: z.ZodString;
28
+ name: z.ZodString;
29
+ arguments: z.ZodString;
30
+ }, z.core.$strip>, z.ZodObject<{
31
+ type: z.ZodLiteral<"functionCallOutput">;
32
+ id: z.ZodString;
33
+ functionCallId: z.ZodString;
34
+ output: z.ZodString;
35
+ status: z.ZodEnum<{
36
+ inProgress: "inProgress";
37
+ completed: "completed";
38
+ incomplete: "incomplete";
39
+ }>;
40
+ }, z.core.$strip>], "type">>;
41
+ }, z.core.$strip>, z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
42
+ type: z.ZodLiteral<"response.output_text.delta">;
43
+ content_index: z.ZodNumber;
44
+ delta: z.ZodString;
45
+ item_id: z.ZodString;
46
+ output_index: z.ZodNumber;
47
+ sequence_number: z.ZodNumber;
48
+ }, z.core.$strip>, z.ZodObject<{
49
+ type: z.ZodLiteral<"response.output_text.done">;
50
+ content_index: z.ZodNumber;
51
+ item_id: z.ZodString;
52
+ output_index: z.ZodNumber;
53
+ sequence_number: z.ZodNumber;
54
+ text: z.ZodString;
55
+ }, z.core.$strip>], "type">>, string, string>, _fragno_dev_core0.FragnoRouteConfig<"GET", "/health", undefined, z.ZodObject<{
56
+ status: z.ZodLiteral<"ok">;
57
+ }, z.core.$strip>, string, string>, _fragno_dev_core0.FragnoRouteConfig<"GET", "/simple-stream", undefined, z.ZodArray<z.ZodObject<{
58
+ message: z.ZodString;
59
+ }, z.core.$strip>>, string, string>], {
60
+ openaiClient: OpenAI;
61
+ }, {
62
+ getOpenAIURL: () => string;
63
+ }>;
64
+ declare function createChatnoClient(fragnoConfig?: FragnoPublicClientConfig): {
65
+ useSendMessage: _fragno_dev_core_client0.FragnoStoreData<{
66
+ readonly response: nanostores0.ReadableAtom<string>;
67
+ readonly responseLoading: nanostores0.ReadableAtom<boolean | undefined>;
68
+ readonly sendMessage: (message: string) => void;
69
+ }>;
70
+ };
71
+ //#endregion
72
+ export { ChatnoServerConfig, type FragnoRouteConfig, createChatno, createChatnoClient };
73
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;UAaiB,kBAAA;;;;;iBAsDD,YAAA,eACA,mCACA,uCAAuB,8CAAA,0CAAA,CAAA,CAAA;EAxDtB,QAAA,YAAA,wBAAkB,CAAA,YAAA,CAAA;IAsDnB,IAAA,cAAY,CAAA,MAAA,CAAA;IAAA,EAAA,aAAA;IACZ,IAAA,WAAA,CAAA;MACA,IAAA,EAAA,MAAA;;;;;;;;;;;;;;;;;;;;;;;;OAAuB,aAAA;;;;;;;;;;;;;;;;;;;;iBAWvB,kBAAA,gBAAiC;;uBAA6B,WAAA,CAAA"}
@@ -0,0 +1,119 @@
1
+ import { createFragment, defineFragment, defineRoute, defineRoutes } from "@fragno-dev/core";
2
+ import { z } from "zod";
3
+ import { createClientBuilder } from "@fragno-dev/core/client";
4
+ import OpenAI from "openai";
5
+ import { computed } from "nanostores";
6
+
7
+ //#region src/server/chatno-api.ts
8
+ const ChatMessageSchema = z.object({
9
+ type: z.literal("chat"),
10
+ id: z.string(),
11
+ role: z.enum(["user", "assistant"]),
12
+ content: z.string()
13
+ });
14
+ const FunctionCallMessageSchema = z.object({
15
+ type: z.literal("functionCall"),
16
+ id: z.string(),
17
+ functionCallId: z.string(),
18
+ name: z.string(),
19
+ arguments: z.string()
20
+ });
21
+ const FunctionCallOutputMessageSchema = z.object({
22
+ type: z.literal("functionCallOutput"),
23
+ id: z.string(),
24
+ functionCallId: z.string(),
25
+ output: z.string(),
26
+ status: z.enum([
27
+ "inProgress",
28
+ "completed",
29
+ "incomplete"
30
+ ])
31
+ });
32
+ const InputMessageSchema = z.discriminatedUnion("type", [
33
+ ChatMessageSchema,
34
+ FunctionCallMessageSchema,
35
+ FunctionCallOutputMessageSchema
36
+ ]);
37
+ const ChatStreamRequestSchema = z.object({ messages: z.array(InputMessageSchema) });
38
+ const ResponseTextDeltaEventSchema = z.object({
39
+ type: z.literal("response.output_text.delta"),
40
+ content_index: z.number(),
41
+ delta: z.string(),
42
+ item_id: z.string(),
43
+ output_index: z.number(),
44
+ sequence_number: z.number()
45
+ });
46
+ const ResponseTextDoneEventSchema = z.object({
47
+ type: z.literal("response.output_text.done"),
48
+ content_index: z.number(),
49
+ item_id: z.string(),
50
+ output_index: z.number(),
51
+ sequence_number: z.number(),
52
+ text: z.string()
53
+ });
54
+ const ResponseEventSchema = z.discriminatedUnion("type", [ResponseTextDeltaEventSchema, ResponseTextDoneEventSchema]);
55
+ const chatRouteFactory = defineRoutes().create(({ config, deps }) => {
56
+ return [defineRoute({
57
+ method: "POST",
58
+ path: "/chat/stream",
59
+ inputSchema: ChatStreamRequestSchema,
60
+ outputSchema: z.array(ResponseEventSchema),
61
+ handler: () => {}
62
+ })];
63
+ });
64
+
65
+ //#endregion
66
+ //#region src/index.ts
67
+ const healthRoute = defineRoute({
68
+ method: "GET",
69
+ path: "/health",
70
+ outputSchema: z.object({ status: z.literal("ok") }),
71
+ handler: () => {}
72
+ });
73
+ const simpleStreamRoute = defineRoute({
74
+ method: "GET",
75
+ path: "/simple-stream",
76
+ outputSchema: z.array(z.object({ message: z.string() })),
77
+ handler: () => {}
78
+ });
79
+ const DEFAULT_SYSTEM_PROMPT = `You are an AI assistant integrated into a dashboard.`;
80
+ const chatnoDefinition = defineFragment("chatno").withDependencies(() => {}).withServices(() => {});
81
+ const routes = [
82
+ chatRouteFactory,
83
+ healthRoute,
84
+ simpleStreamRoute
85
+ ];
86
+ function createChatno(chatnoConfig, fragnoConfig = {}) {
87
+ const config = {
88
+ model: chatnoConfig.model ?? "gpt-5-nano",
89
+ systemPrompt: chatnoConfig.systemPrompt ?? DEFAULT_SYSTEM_PROMPT
90
+ };
91
+ return createFragment(chatnoDefinition, {
92
+ ...chatnoConfig,
93
+ ...config
94
+ }, routes, fragnoConfig);
95
+ }
96
+ function createChatnoClient(fragnoConfig = {}) {
97
+ const cb = createClientBuilder(chatnoDefinition, fragnoConfig, routes);
98
+ const chatStream = cb.createMutator("POST", "/chat/stream");
99
+ const aggregatedMessage = computed(chatStream.mutatorStore, ({ data }) => {
100
+ return (data ?? []).filter((item) => item.type === "response.output_text.delta").map((item) => item.delta).join("");
101
+ });
102
+ function sendMessage(message) {
103
+ chatStream.mutatorStore.mutate({ body: { messages: [{
104
+ type: "chat",
105
+ id: crypto.randomUUID(),
106
+ role: "user",
107
+ content: message
108
+ }] } });
109
+ }
110
+ return { useSendMessage: cb.createStore({
111
+ response: aggregatedMessage,
112
+ responseLoading: computed(chatStream.mutatorStore, ({ loading }) => loading),
113
+ sendMessage
114
+ }) };
115
+ }
116
+
117
+ //#endregion
118
+ export { createChatno, createChatnoClient };
119
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["OpenAI","z","defineRoute","defineRoutes","ChatMessageSchema","object","type","literal","id","string","role","enum","content","FunctionCallMessageSchema","functionCallId","name","arguments","FunctionCallOutputMessageSchema","output","status","InputMessageSchema","discriminatedUnion","ChatStreamRequestSchema","messages","array","ResponseTextDeltaEventSchema","content_index","number","delta","item_id","output_index","sequence_number","ResponseTextDoneEventSchema","text","ResponseEventSchema","ChatRouteConfig","model","systemPrompt","ChatRouteDeps","openaiClient","chatRouteFactory","create","config","deps","method","path","inputSchema","outputSchema","handler","defineFragment","defineRoute","createFragment","FragnoPublicClientConfig","FragnoPublicConfig","z","createClientBuilder","chatRouteFactory","computed","ChatnoServerConfig","openaiApiKey","model","systemPrompt","healthRoute","method","path","outputSchema","object","status","literal","handler","simpleStreamRoute","array","message","string","DEFAULT_SYSTEM_PROMPT","chatnoDefinition","withDependencies","withServices","routes","const","createChatno","chatnoConfig","fragnoConfig","config","createChatnoClient","cb","chatStream","createMutator","aggregatedMessage","mutatorStore","data","filter","item","type","map","delta","join","sendMessage","mutate","body","messages","id","crypto","randomUUID","role","content","useSendMessage","createStore","response","responseLoading","loading","FragnoRouteConfig"],"sources":["../../src/server/chatno-api.ts","../../src/index.ts"],"sourcesContent":["import OpenAI from \"openai\";\nimport { z } from \"zod\";\nimport type {\n EasyInputMessage,\n ResponseFunctionToolCall,\n ResponseInputItem,\n} from \"openai/resources/responses/responses.mjs\";\nimport { defineRoute, defineRoutes } from \"@fragno-dev/core\";\n\nexport const ChatMessageSchema = z.object({\n type: z.literal(\"chat\"),\n id: z.string(),\n role: z.enum([\"user\", \"assistant\"]),\n content: z.string(),\n});\n\nexport const FunctionCallMessageSchema = z.object({\n type: z.literal(\"functionCall\"),\n id: z.string(),\n functionCallId: z.string(),\n name: z.string(),\n arguments: z.string(),\n});\n\nexport const FunctionCallOutputMessageSchema = z.object({\n type: z.literal(\"functionCallOutput\"),\n id: z.string(),\n functionCallId: z.string(),\n output: z.string(),\n status: z.enum([\"inProgress\", \"completed\", \"incomplete\"]),\n});\n\nexport const InputMessageSchema = z.discriminatedUnion(\"type\", [\n ChatMessageSchema,\n FunctionCallMessageSchema,\n FunctionCallOutputMessageSchema,\n]);\n\n// Request schema with unified messages array\nexport const ChatStreamRequestSchema = z.object({\n messages: z.array(InputMessageSchema),\n});\n\nexport const ResponseTextDeltaEventSchema = z.object({\n type: z.literal(\"response.output_text.delta\"),\n content_index: z.number(),\n delta: z.string(),\n item_id: z.string(),\n output_index: z.number(),\n sequence_number: z.number(),\n});\n\nexport const ResponseTextDoneEventSchema = z.object({\n type: z.literal(\"response.output_text.done\"),\n content_index: z.number(),\n item_id: z.string(),\n output_index: z.number(),\n sequence_number: z.number(),\n text: z.string(),\n});\n\nexport const ResponseEventSchema = z.discriminatedUnion(\"type\", [\n ResponseTextDeltaEventSchema,\n ResponseTextDoneEventSchema,\n]);\n\ntype ChatRouteConfig = {\n model: \"gpt-5-mini\" | \"4o-mini\";\n systemPrompt: string;\n};\n\ntype ChatRouteDeps = {\n openaiClient: OpenAI;\n};\n\nexport const chatRouteFactory = defineRoutes<ChatRouteConfig, ChatRouteDeps>().create(\n ({ config, deps }) => {\n const { openaiClient } = deps;\n const { model, systemPrompt } = config;\n\n return [\n defineRoute({\n method: \"POST\",\n path: \"/chat/stream\",\n inputSchema: ChatStreamRequestSchema,\n outputSchema: z.array(ResponseEventSchema),\n handler: async ({ input }, { jsonStream }) => {\n const { messages } = await input.valid();\n\n const openAIMessages: ResponseInputItem[] = messages.map((message) => {\n if (message.type === \"chat\") {\n return {\n role: message.role,\n content: message.content,\n } satisfies EasyInputMessage;\n }\n\n if (message.type === \"functionCall\") {\n return {\n type: \"function_call\",\n id: message.id,\n call_id: message.functionCallId,\n name: message.name,\n arguments: message.arguments,\n } satisfies ResponseFunctionToolCall;\n }\n\n if (message.type === \"functionCallOutput\") {\n return {\n type: \"function_call_output\",\n call_id: message.functionCallId,\n output: message.output,\n status:\n message.status === \"inProgress\"\n ? \"in_progress\"\n : message.status === \"completed\"\n ? \"completed\"\n : \"incomplete\",\n } satisfies ResponseInputItem.FunctionCallOutput;\n }\n\n throw new Error(\"unreachable\");\n });\n\n try {\n const eventStream = await openaiClient.responses.create({\n model,\n input: [\n {\n role: \"system\",\n content: systemPrompt,\n },\n ...openAIMessages,\n ],\n // tools: [],\n // tool_choice: \"auto\",\n stream: true,\n });\n\n return jsonStream(async (stream) => {\n for await (const event of eventStream) {\n if (\n event.type !== \"response.output_text.delta\" &&\n event.type !== \"response.output_text.done\"\n ) {\n continue;\n }\n\n await stream.write(event);\n }\n });\n } catch (error) {\n console.error(\"OpenAI API error:\", error);\n throw error;\n }\n },\n }),\n ];\n },\n);\n","import {\n defineFragment,\n defineRoute,\n createFragment,\n type FragnoPublicClientConfig,\n type FragnoPublicConfig,\n} from \"@fragno-dev/core\";\nimport OpenAI from \"openai\";\nimport { z } from \"zod\";\nimport { createClientBuilder } from \"@fragno-dev/core/client\";\nimport { chatRouteFactory } from \"./server/chatno-api\";\nimport { computed } from \"nanostores\";\n\nexport interface ChatnoServerConfig {\n openaiApiKey: string;\n model?: \"gpt-5-mini\" | \"4o-mini\" | \"gpt-5-nano\";\n systemPrompt?: string;\n}\n\nconst healthRoute = defineRoute({\n method: \"GET\",\n path: \"/health\",\n outputSchema: z.object({\n status: z.literal(\"ok\"),\n }),\n handler: async (_ctx, { json }) => {\n return json({ status: \"ok\" });\n },\n});\n\nconst simpleStreamRoute = defineRoute({\n method: \"GET\",\n path: \"/simple-stream\",\n outputSchema: z.array(\n z.object({\n message: z.string(),\n }),\n ),\n handler: async (_ctx, { jsonStream }) => {\n return jsonStream(async (stream) => {\n for (let i = 0; i < 10; i++) {\n await stream.sleep(500);\n await stream.write({ message: `Item ${i + 1}` });\n }\n });\n },\n});\n\nconst DEFAULT_SYSTEM_PROMPT = `You are an AI assistant integrated into a dashboard.`;\n\nconst chatnoDefinition = defineFragment<ChatnoServerConfig>(\"chatno\")\n .withDependencies((config: ChatnoServerConfig) => {\n return {\n openaiClient: new OpenAI({\n apiKey: config.openaiApiKey,\n }),\n };\n })\n .withServices((_cfg, deps) => {\n return {\n getOpenAIURL: () => deps.openaiClient.baseURL,\n };\n });\n\nconst routes = [chatRouteFactory, healthRoute, simpleStreamRoute] as const;\n\n// Server-side factory\nexport function createChatno(\n chatnoConfig: ChatnoServerConfig,\n fragnoConfig: FragnoPublicConfig = {},\n) {\n const config = {\n model: chatnoConfig.model ?? \"gpt-5-nano\",\n systemPrompt: chatnoConfig.systemPrompt ?? DEFAULT_SYSTEM_PROMPT,\n };\n\n return createFragment(chatnoDefinition, { ...chatnoConfig, ...config }, routes, fragnoConfig);\n}\n\n// Client-side factory\nexport function createChatnoClient(fragnoConfig: FragnoPublicClientConfig = {}) {\n const cb = createClientBuilder(chatnoDefinition, fragnoConfig, routes);\n\n const chatStream = cb.createMutator(\"POST\", \"/chat/stream\");\n\n const aggregatedMessage = computed(chatStream.mutatorStore, ({ data }) => {\n return (data ?? [])\n .filter((item) => item.type === \"response.output_text.delta\")\n .map((item) => item.delta)\n .join(\"\");\n });\n\n function sendMessage(message: string) {\n chatStream.mutatorStore.mutate({\n body: {\n messages: [{ type: \"chat\", id: crypto.randomUUID(), role: \"user\", content: message }],\n },\n });\n }\n\n return {\n useSendMessage: cb.createStore({\n response: aggregatedMessage,\n responseLoading: computed(chatStream.mutatorStore, ({ loading }) => loading),\n sendMessage,\n }),\n };\n}\n\nexport type { FragnoRouteConfig } from \"@fragno-dev/core/api\";\n"],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import * as _fragno_dev_core0 from "@fragno-dev/core";
2
+ import { FragnoPublicClientConfig, FragnoPublicConfig } from "@fragno-dev/core";
3
+ import OpenAI from "openai";
4
+ import { z } from "zod";
5
+ import * as _fragno_dev_core_client0 from "@fragno-dev/core/client";
6
+ import * as nanostores0 from "nanostores";
7
+ import { FragnoRouteConfig } from "@fragno-dev/core/api";
8
+
9
+ //#region src/index.d.ts
10
+ interface ChatnoServerConfig {
11
+ openaiApiKey: string;
12
+ model?: "gpt-5-mini" | "4o-mini" | "gpt-5-nano";
13
+ systemPrompt?: string;
14
+ }
15
+ declare function createChatno(chatnoConfig: ChatnoServerConfig, fragnoConfig?: FragnoPublicConfig): _fragno_dev_core0.FragnoInstantiatedFragment<[_fragno_dev_core0.FragnoRouteConfig<"POST", "/chat/stream", z.ZodObject<{
16
+ messages: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
17
+ type: z.ZodLiteral<"chat">;
18
+ id: z.ZodString;
19
+ role: z.ZodEnum<{
20
+ user: "user";
21
+ assistant: "assistant";
22
+ }>;
23
+ content: z.ZodString;
24
+ }, z.core.$strip>, z.ZodObject<{
25
+ type: z.ZodLiteral<"functionCall">;
26
+ id: z.ZodString;
27
+ functionCallId: z.ZodString;
28
+ name: z.ZodString;
29
+ arguments: z.ZodString;
30
+ }, z.core.$strip>, z.ZodObject<{
31
+ type: z.ZodLiteral<"functionCallOutput">;
32
+ id: z.ZodString;
33
+ functionCallId: z.ZodString;
34
+ output: z.ZodString;
35
+ status: z.ZodEnum<{
36
+ inProgress: "inProgress";
37
+ completed: "completed";
38
+ incomplete: "incomplete";
39
+ }>;
40
+ }, z.core.$strip>], "type">>;
41
+ }, z.core.$strip>, z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
42
+ type: z.ZodLiteral<"response.output_text.delta">;
43
+ content_index: z.ZodNumber;
44
+ delta: z.ZodString;
45
+ item_id: z.ZodString;
46
+ output_index: z.ZodNumber;
47
+ sequence_number: z.ZodNumber;
48
+ }, z.core.$strip>, z.ZodObject<{
49
+ type: z.ZodLiteral<"response.output_text.done">;
50
+ content_index: z.ZodNumber;
51
+ item_id: z.ZodString;
52
+ output_index: z.ZodNumber;
53
+ sequence_number: z.ZodNumber;
54
+ text: z.ZodString;
55
+ }, z.core.$strip>], "type">>, string, string>, _fragno_dev_core0.FragnoRouteConfig<"GET", "/health", undefined, z.ZodObject<{
56
+ status: z.ZodLiteral<"ok">;
57
+ }, z.core.$strip>, string, string>, _fragno_dev_core0.FragnoRouteConfig<"GET", "/simple-stream", undefined, z.ZodArray<z.ZodObject<{
58
+ message: z.ZodString;
59
+ }, z.core.$strip>>, string, string>], {
60
+ openaiClient: OpenAI;
61
+ }, {
62
+ getOpenAIURL: () => string;
63
+ }>;
64
+ declare function createChatnoClient(fragnoConfig?: FragnoPublicClientConfig): {
65
+ useSendMessage: _fragno_dev_core_client0.FragnoStoreData<{
66
+ readonly response: nanostores0.ReadableAtom<string>;
67
+ readonly responseLoading: nanostores0.ReadableAtom<boolean | undefined>;
68
+ readonly sendMessage: (message: string) => void;
69
+ }>;
70
+ };
71
+ //#endregion
72
+ export { ChatnoServerConfig, type FragnoRouteConfig, createChatno, createChatnoClient };
73
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;UAaiB,kBAAA;;;;;iBAsDD,YAAA,eACA,mCACA,uCAAuB,8CAAA,0CAAA,CAAA,CAAA;EAxDtB,QAAA,YAAA,wBAAkB,CAAA,YAAA,CAAA;IAsDnB,IAAA,cAAY,CAAA,MAAA,CAAA;IAAA,EAAA,aAAA;IACZ,IAAA,WAAA,CAAA;MACA,IAAA,EAAA,MAAA;;;;;;;;;;;;;;;;;;;;;;;;OAAuB,aAAA;;;;;;;;;;;;;;;;;;;;iBAWvB,kBAAA,gBAAiC;;uBAA6B,WAAA,CAAA"}
@@ -0,0 +1,74 @@
1
+ import { chatRouteFactory } from "./server/chatno-api.js";
2
+ import { createFragment, defineFragment, defineRoute } from "@fragno-dev/core";
3
+ import OpenAI from "openai";
4
+ import { z } from "zod";
5
+ import { createClientBuilder } from "@fragno-dev/core/client";
6
+ import { computed } from "nanostores";
7
+
8
+ //#region src/index.ts
9
+ const healthRoute = defineRoute({
10
+ method: "GET",
11
+ path: "/health",
12
+ outputSchema: z.object({ status: z.literal("ok") }),
13
+ handler: async (_ctx, { json }) => {
14
+ return json({ status: "ok" });
15
+ }
16
+ });
17
+ const simpleStreamRoute = defineRoute({
18
+ method: "GET",
19
+ path: "/simple-stream",
20
+ outputSchema: z.array(z.object({ message: z.string() })),
21
+ handler: async (_ctx, { jsonStream }) => {
22
+ return jsonStream(async (stream) => {
23
+ for (let i = 0; i < 10; i++) {
24
+ await stream.sleep(500);
25
+ await stream.write({ message: `Item ${i + 1}` });
26
+ }
27
+ });
28
+ }
29
+ });
30
+ const DEFAULT_SYSTEM_PROMPT = `You are an AI assistant integrated into a dashboard.`;
31
+ const chatnoDefinition = defineFragment("chatno").withDependencies((config) => {
32
+ return { openaiClient: new OpenAI({ apiKey: config.openaiApiKey }) };
33
+ }).withServices((_cfg, deps) => {
34
+ return { getOpenAIURL: () => deps.openaiClient.baseURL };
35
+ });
36
+ const routes = [
37
+ chatRouteFactory,
38
+ healthRoute,
39
+ simpleStreamRoute
40
+ ];
41
+ function createChatno(chatnoConfig, fragnoConfig = {}) {
42
+ const config = {
43
+ model: chatnoConfig.model ?? "gpt-5-nano",
44
+ systemPrompt: chatnoConfig.systemPrompt ?? DEFAULT_SYSTEM_PROMPT
45
+ };
46
+ return createFragment(chatnoDefinition, {
47
+ ...chatnoConfig,
48
+ ...config
49
+ }, routes, fragnoConfig);
50
+ }
51
+ function createChatnoClient(fragnoConfig = {}) {
52
+ const cb = createClientBuilder(chatnoDefinition, fragnoConfig, routes);
53
+ const chatStream = cb.createMutator("POST", "/chat/stream");
54
+ const aggregatedMessage = computed(chatStream.mutatorStore, ({ data }) => {
55
+ return (data ?? []).filter((item) => item.type === "response.output_text.delta").map((item) => item.delta).join("");
56
+ });
57
+ function sendMessage(message) {
58
+ chatStream.mutatorStore.mutate({ body: { messages: [{
59
+ type: "chat",
60
+ id: crypto.randomUUID(),
61
+ role: "user",
62
+ content: message
63
+ }] } });
64
+ }
65
+ return { useSendMessage: cb.createStore({
66
+ response: aggregatedMessage,
67
+ responseLoading: computed(chatStream.mutatorStore, ({ loading }) => loading),
68
+ sendMessage
69
+ }) };
70
+ }
71
+
72
+ //#endregion
73
+ export { createChatno, createChatnoClient };
74
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/index.ts"],"sourcesContent":["import {\n defineFragment,\n defineRoute,\n createFragment,\n type FragnoPublicClientConfig,\n type FragnoPublicConfig,\n} from \"@fragno-dev/core\";\nimport OpenAI from \"openai\";\nimport { z } from \"zod\";\nimport { createClientBuilder } from \"@fragno-dev/core/client\";\nimport { chatRouteFactory } from \"./server/chatno-api\";\nimport { computed } from \"nanostores\";\n\nexport interface ChatnoServerConfig {\n openaiApiKey: string;\n model?: \"gpt-5-mini\" | \"4o-mini\" | \"gpt-5-nano\";\n systemPrompt?: string;\n}\n\nconst healthRoute = defineRoute({\n method: \"GET\",\n path: \"/health\",\n outputSchema: z.object({\n status: z.literal(\"ok\"),\n }),\n handler: async (_ctx, { json }) => {\n return json({ status: \"ok\" });\n },\n});\n\nconst simpleStreamRoute = defineRoute({\n method: \"GET\",\n path: \"/simple-stream\",\n outputSchema: z.array(\n z.object({\n message: z.string(),\n }),\n ),\n handler: async (_ctx, { jsonStream }) => {\n return jsonStream(async (stream) => {\n for (let i = 0; i < 10; i++) {\n await stream.sleep(500);\n await stream.write({ message: `Item ${i + 1}` });\n }\n });\n },\n});\n\nconst DEFAULT_SYSTEM_PROMPT = `You are an AI assistant integrated into a dashboard.`;\n\nconst chatnoDefinition = defineFragment<ChatnoServerConfig>(\"chatno\")\n .withDependencies((config: ChatnoServerConfig) => {\n return {\n openaiClient: new OpenAI({\n apiKey: config.openaiApiKey,\n }),\n };\n })\n .withServices((_cfg, deps) => {\n return {\n getOpenAIURL: () => deps.openaiClient.baseURL,\n };\n });\n\nconst routes = [chatRouteFactory, healthRoute, simpleStreamRoute] as const;\n\n// Server-side factory\nexport function createChatno(\n chatnoConfig: ChatnoServerConfig,\n fragnoConfig: FragnoPublicConfig = {},\n) {\n const config = {\n model: chatnoConfig.model ?? \"gpt-5-nano\",\n systemPrompt: chatnoConfig.systemPrompt ?? DEFAULT_SYSTEM_PROMPT,\n };\n\n return createFragment(chatnoDefinition, { ...chatnoConfig, ...config }, routes, fragnoConfig);\n}\n\n// Client-side factory\nexport function createChatnoClient(fragnoConfig: FragnoPublicClientConfig = {}) {\n const cb = createClientBuilder(chatnoDefinition, fragnoConfig, routes);\n\n const chatStream = cb.createMutator(\"POST\", \"/chat/stream\");\n\n const aggregatedMessage = computed(chatStream.mutatorStore, ({ data }) => {\n return (data ?? [])\n .filter((item) => item.type === \"response.output_text.delta\")\n .map((item) => item.delta)\n .join(\"\");\n });\n\n function sendMessage(message: string) {\n chatStream.mutatorStore.mutate({\n body: {\n messages: [{ type: \"chat\", id: crypto.randomUUID(), role: \"user\", content: message }],\n },\n });\n }\n\n return {\n useSendMessage: cb.createStore({\n response: aggregatedMessage,\n responseLoading: computed(chatStream.mutatorStore, ({ loading }) => loading),\n sendMessage,\n }),\n };\n}\n\nexport type { FragnoRouteConfig } from \"@fragno-dev/core/api\";\n"],"mappings":";;;;;;;;AAmBA,MAAM,cAAc,YAAY;CAC9B,QAAQ;CACR,MAAM;CACN,cAAc,EAAE,OAAO,EACrB,QAAQ,EAAE,QAAQ,KAAK,EACxB,CAAC;CACF,SAAS,OAAO,MAAM,EAAE,WAAW;AACjC,SAAO,KAAK,EAAE,QAAQ,MAAM,CAAC;;CAEhC,CAAC;AAEF,MAAM,oBAAoB,YAAY;CACpC,QAAQ;CACR,MAAM;CACN,cAAc,EAAE,MACd,EAAE,OAAO,EACP,SAAS,EAAE,QAAQ,EACpB,CAAC,CACH;CACD,SAAS,OAAO,MAAM,EAAE,iBAAiB;AACvC,SAAO,WAAW,OAAO,WAAW;AAClC,QAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,UAAM,OAAO,MAAM,IAAI;AACvB,UAAM,OAAO,MAAM,EAAE,SAAS,QAAQ,IAAI,KAAK,CAAC;;IAElD;;CAEL,CAAC;AAEF,MAAM,wBAAwB;AAE9B,MAAM,mBAAmB,eAAmC,SAAS,CAClE,kBAAkB,WAA+B;AAChD,QAAO,EACL,cAAc,IAAI,OAAO,EACvB,QAAQ,OAAO,cAChB,CAAC,EACH;EACD,CACD,cAAc,MAAM,SAAS;AAC5B,QAAO,EACL,oBAAoB,KAAK,aAAa,SACvC;EACD;AAEJ,MAAM,SAAS;CAAC;CAAkB;CAAa;CAAkB;AAGjE,SAAgB,aACd,cACA,eAAmC,EAAE,EACrC;CACA,MAAM,SAAS;EACb,OAAO,aAAa,SAAS;EAC7B,cAAc,aAAa,gBAAgB;EAC5C;AAED,QAAO,eAAe,kBAAkB;EAAE,GAAG;EAAc,GAAG;EAAQ,EAAE,QAAQ,aAAa;;AAI/F,SAAgB,mBAAmB,eAAyC,EAAE,EAAE;CAC9E,MAAM,KAAK,oBAAoB,kBAAkB,cAAc,OAAO;CAEtE,MAAM,aAAa,GAAG,cAAc,QAAQ,eAAe;CAE3D,MAAM,oBAAoB,SAAS,WAAW,eAAe,EAAE,WAAW;AACxE,UAAQ,QAAQ,EAAE,EACf,QAAQ,SAAS,KAAK,SAAS,6BAA6B,CAC5D,KAAK,SAAS,KAAK,MAAM,CACzB,KAAK,GAAG;GACX;CAEF,SAAS,YAAY,SAAiB;AACpC,aAAW,aAAa,OAAO,EAC7B,MAAM,EACJ,UAAU,CAAC;GAAE,MAAM;GAAQ,IAAI,OAAO,YAAY;GAAE,MAAM;GAAQ,SAAS;GAAS,CAAC,EACtF,EACF,CAAC;;AAGJ,QAAO,EACL,gBAAgB,GAAG,YAAY;EAC7B,UAAU;EACV,iBAAiB,SAAS,WAAW,eAAe,EAAE,cAAc,QAAQ;EAC5E;EACD,CAAC,EACH"}
@@ -0,0 +1,108 @@
1
+ import { defineRoute, defineRoutes } from "@fragno-dev/core";
2
+ import OpenAI from "openai";
3
+ import { z } from "zod";
4
+
5
+ //#region src/server/chatno-api.ts
6
+ const ChatMessageSchema = z.object({
7
+ type: z.literal("chat"),
8
+ id: z.string(),
9
+ role: z.enum(["user", "assistant"]),
10
+ content: z.string()
11
+ });
12
+ const FunctionCallMessageSchema = z.object({
13
+ type: z.literal("functionCall"),
14
+ id: z.string(),
15
+ functionCallId: z.string(),
16
+ name: z.string(),
17
+ arguments: z.string()
18
+ });
19
+ const FunctionCallOutputMessageSchema = z.object({
20
+ type: z.literal("functionCallOutput"),
21
+ id: z.string(),
22
+ functionCallId: z.string(),
23
+ output: z.string(),
24
+ status: z.enum([
25
+ "inProgress",
26
+ "completed",
27
+ "incomplete"
28
+ ])
29
+ });
30
+ const InputMessageSchema = z.discriminatedUnion("type", [
31
+ ChatMessageSchema,
32
+ FunctionCallMessageSchema,
33
+ FunctionCallOutputMessageSchema
34
+ ]);
35
+ const ChatStreamRequestSchema = z.object({ messages: z.array(InputMessageSchema) });
36
+ const ResponseTextDeltaEventSchema = z.object({
37
+ type: z.literal("response.output_text.delta"),
38
+ content_index: z.number(),
39
+ delta: z.string(),
40
+ item_id: z.string(),
41
+ output_index: z.number(),
42
+ sequence_number: z.number()
43
+ });
44
+ const ResponseTextDoneEventSchema = z.object({
45
+ type: z.literal("response.output_text.done"),
46
+ content_index: z.number(),
47
+ item_id: z.string(),
48
+ output_index: z.number(),
49
+ sequence_number: z.number(),
50
+ text: z.string()
51
+ });
52
+ const ResponseEventSchema = z.discriminatedUnion("type", [ResponseTextDeltaEventSchema, ResponseTextDoneEventSchema]);
53
+ const chatRouteFactory = defineRoutes().create(({ config, deps }) => {
54
+ const { openaiClient } = deps;
55
+ const { model, systemPrompt } = config;
56
+ return [defineRoute({
57
+ method: "POST",
58
+ path: "/chat/stream",
59
+ inputSchema: ChatStreamRequestSchema,
60
+ outputSchema: z.array(ResponseEventSchema),
61
+ handler: async ({ input }, { jsonStream }) => {
62
+ const { messages } = await input.valid();
63
+ const openAIMessages = messages.map((message) => {
64
+ if (message.type === "chat") return {
65
+ role: message.role,
66
+ content: message.content
67
+ };
68
+ if (message.type === "functionCall") return {
69
+ type: "function_call",
70
+ id: message.id,
71
+ call_id: message.functionCallId,
72
+ name: message.name,
73
+ arguments: message.arguments
74
+ };
75
+ if (message.type === "functionCallOutput") return {
76
+ type: "function_call_output",
77
+ call_id: message.functionCallId,
78
+ output: message.output,
79
+ status: message.status === "inProgress" ? "in_progress" : message.status === "completed" ? "completed" : "incomplete"
80
+ };
81
+ throw new Error("unreachable");
82
+ });
83
+ try {
84
+ const eventStream = await openaiClient.responses.create({
85
+ model,
86
+ input: [{
87
+ role: "system",
88
+ content: systemPrompt
89
+ }, ...openAIMessages],
90
+ stream: true
91
+ });
92
+ return jsonStream(async (stream) => {
93
+ for await (const event of eventStream) {
94
+ if (event.type !== "response.output_text.delta" && event.type !== "response.output_text.done") continue;
95
+ await stream.write(event);
96
+ }
97
+ });
98
+ } catch (error) {
99
+ console.error("OpenAI API error:", error);
100
+ throw error;
101
+ }
102
+ }
103
+ })];
104
+ });
105
+
106
+ //#endregion
107
+ export { chatRouteFactory };
108
+ //# sourceMappingURL=chatno-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chatno-api.js","names":["openAIMessages: ResponseInputItem[]"],"sources":["../../../src/server/chatno-api.ts"],"sourcesContent":["import OpenAI from \"openai\";\nimport { z } from \"zod\";\nimport type {\n EasyInputMessage,\n ResponseFunctionToolCall,\n ResponseInputItem,\n} from \"openai/resources/responses/responses.mjs\";\nimport { defineRoute, defineRoutes } from \"@fragno-dev/core\";\n\nexport const ChatMessageSchema = z.object({\n type: z.literal(\"chat\"),\n id: z.string(),\n role: z.enum([\"user\", \"assistant\"]),\n content: z.string(),\n});\n\nexport const FunctionCallMessageSchema = z.object({\n type: z.literal(\"functionCall\"),\n id: z.string(),\n functionCallId: z.string(),\n name: z.string(),\n arguments: z.string(),\n});\n\nexport const FunctionCallOutputMessageSchema = z.object({\n type: z.literal(\"functionCallOutput\"),\n id: z.string(),\n functionCallId: z.string(),\n output: z.string(),\n status: z.enum([\"inProgress\", \"completed\", \"incomplete\"]),\n});\n\nexport const InputMessageSchema = z.discriminatedUnion(\"type\", [\n ChatMessageSchema,\n FunctionCallMessageSchema,\n FunctionCallOutputMessageSchema,\n]);\n\n// Request schema with unified messages array\nexport const ChatStreamRequestSchema = z.object({\n messages: z.array(InputMessageSchema),\n});\n\nexport const ResponseTextDeltaEventSchema = z.object({\n type: z.literal(\"response.output_text.delta\"),\n content_index: z.number(),\n delta: z.string(),\n item_id: z.string(),\n output_index: z.number(),\n sequence_number: z.number(),\n});\n\nexport const ResponseTextDoneEventSchema = z.object({\n type: z.literal(\"response.output_text.done\"),\n content_index: z.number(),\n item_id: z.string(),\n output_index: z.number(),\n sequence_number: z.number(),\n text: z.string(),\n});\n\nexport const ResponseEventSchema = z.discriminatedUnion(\"type\", [\n ResponseTextDeltaEventSchema,\n ResponseTextDoneEventSchema,\n]);\n\ntype ChatRouteConfig = {\n model: \"gpt-5-mini\" | \"4o-mini\";\n systemPrompt: string;\n};\n\ntype ChatRouteDeps = {\n openaiClient: OpenAI;\n};\n\nexport const chatRouteFactory = defineRoutes<ChatRouteConfig, ChatRouteDeps>().create(\n ({ config, deps }) => {\n const { openaiClient } = deps;\n const { model, systemPrompt } = config;\n\n return [\n defineRoute({\n method: \"POST\",\n path: \"/chat/stream\",\n inputSchema: ChatStreamRequestSchema,\n outputSchema: z.array(ResponseEventSchema),\n handler: async ({ input }, { jsonStream }) => {\n const { messages } = await input.valid();\n\n const openAIMessages: ResponseInputItem[] = messages.map((message) => {\n if (message.type === \"chat\") {\n return {\n role: message.role,\n content: message.content,\n } satisfies EasyInputMessage;\n }\n\n if (message.type === \"functionCall\") {\n return {\n type: \"function_call\",\n id: message.id,\n call_id: message.functionCallId,\n name: message.name,\n arguments: message.arguments,\n } satisfies ResponseFunctionToolCall;\n }\n\n if (message.type === \"functionCallOutput\") {\n return {\n type: \"function_call_output\",\n call_id: message.functionCallId,\n output: message.output,\n status:\n message.status === \"inProgress\"\n ? \"in_progress\"\n : message.status === \"completed\"\n ? \"completed\"\n : \"incomplete\",\n } satisfies ResponseInputItem.FunctionCallOutput;\n }\n\n throw new Error(\"unreachable\");\n });\n\n try {\n const eventStream = await openaiClient.responses.create({\n model,\n input: [\n {\n role: \"system\",\n content: systemPrompt,\n },\n ...openAIMessages,\n ],\n // tools: [],\n // tool_choice: \"auto\",\n stream: true,\n });\n\n return jsonStream(async (stream) => {\n for await (const event of eventStream) {\n if (\n event.type !== \"response.output_text.delta\" &&\n event.type !== \"response.output_text.done\"\n ) {\n continue;\n }\n\n await stream.write(event);\n }\n });\n } catch (error) {\n console.error(\"OpenAI API error:\", error);\n throw error;\n }\n },\n }),\n ];\n },\n);\n"],"mappings":";;;;;AASA,MAAa,oBAAoB,EAAE,OAAO;CACxC,MAAM,EAAE,QAAQ,OAAO;CACvB,IAAI,EAAE,QAAQ;CACd,MAAM,EAAE,KAAK,CAAC,QAAQ,YAAY,CAAC;CACnC,SAAS,EAAE,QAAQ;CACpB,CAAC;AAEF,MAAa,4BAA4B,EAAE,OAAO;CAChD,MAAM,EAAE,QAAQ,eAAe;CAC/B,IAAI,EAAE,QAAQ;CACd,gBAAgB,EAAE,QAAQ;CAC1B,MAAM,EAAE,QAAQ;CAChB,WAAW,EAAE,QAAQ;CACtB,CAAC;AAEF,MAAa,kCAAkC,EAAE,OAAO;CACtD,MAAM,EAAE,QAAQ,qBAAqB;CACrC,IAAI,EAAE,QAAQ;CACd,gBAAgB,EAAE,QAAQ;CAC1B,QAAQ,EAAE,QAAQ;CAClB,QAAQ,EAAE,KAAK;EAAC;EAAc;EAAa;EAAa,CAAC;CAC1D,CAAC;AAEF,MAAa,qBAAqB,EAAE,mBAAmB,QAAQ;CAC7D;CACA;CACA;CACD,CAAC;AAGF,MAAa,0BAA0B,EAAE,OAAO,EAC9C,UAAU,EAAE,MAAM,mBAAmB,EACtC,CAAC;AAEF,MAAa,+BAA+B,EAAE,OAAO;CACnD,MAAM,EAAE,QAAQ,6BAA6B;CAC7C,eAAe,EAAE,QAAQ;CACzB,OAAO,EAAE,QAAQ;CACjB,SAAS,EAAE,QAAQ;CACnB,cAAc,EAAE,QAAQ;CACxB,iBAAiB,EAAE,QAAQ;CAC5B,CAAC;AAEF,MAAa,8BAA8B,EAAE,OAAO;CAClD,MAAM,EAAE,QAAQ,4BAA4B;CAC5C,eAAe,EAAE,QAAQ;CACzB,SAAS,EAAE,QAAQ;CACnB,cAAc,EAAE,QAAQ;CACxB,iBAAiB,EAAE,QAAQ;CAC3B,MAAM,EAAE,QAAQ;CACjB,CAAC;AAEF,MAAa,sBAAsB,EAAE,mBAAmB,QAAQ,CAC9D,8BACA,4BACD,CAAC;AAWF,MAAa,mBAAmB,cAA8C,CAAC,QAC5E,EAAE,QAAQ,WAAW;CACpB,MAAM,EAAE,iBAAiB;CACzB,MAAM,EAAE,OAAO,iBAAiB;AAEhC,QAAO,CACL,YAAY;EACV,QAAQ;EACR,MAAM;EACN,aAAa;EACb,cAAc,EAAE,MAAM,oBAAoB;EAC1C,SAAS,OAAO,EAAE,SAAS,EAAE,iBAAiB;GAC5C,MAAM,EAAE,aAAa,MAAM,MAAM,OAAO;GAExC,MAAMA,iBAAsC,SAAS,KAAK,YAAY;AACpE,QAAI,QAAQ,SAAS,OACnB,QAAO;KACL,MAAM,QAAQ;KACd,SAAS,QAAQ;KAClB;AAGH,QAAI,QAAQ,SAAS,eACnB,QAAO;KACL,MAAM;KACN,IAAI,QAAQ;KACZ,SAAS,QAAQ;KACjB,MAAM,QAAQ;KACd,WAAW,QAAQ;KACpB;AAGH,QAAI,QAAQ,SAAS,qBACnB,QAAO;KACL,MAAM;KACN,SAAS,QAAQ;KACjB,QAAQ,QAAQ;KAChB,QACE,QAAQ,WAAW,eACf,gBACA,QAAQ,WAAW,cACjB,cACA;KACT;AAGH,UAAM,IAAI,MAAM,cAAc;KAC9B;AAEF,OAAI;IACF,MAAM,cAAc,MAAM,aAAa,UAAU,OAAO;KACtD;KACA,OAAO,CACL;MACE,MAAM;MACN,SAAS;MACV,EACD,GAAG,eACJ;KAGD,QAAQ;KACT,CAAC;AAEF,WAAO,WAAW,OAAO,WAAW;AAClC,gBAAW,MAAM,SAAS,aAAa;AACrC,UACE,MAAM,SAAS,gCACf,MAAM,SAAS,4BAEf;AAGF,YAAM,OAAO,MAAM,MAAM;;MAE3B;YACK,OAAO;AACd,YAAQ,MAAM,qBAAqB,MAAM;AACzC,UAAM;;;EAGX,CAAC,CACH;EAEJ"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@fragno-dev/chatno",
3
+ "version": "0.0.5",
4
+ "exports": {
5
+ ".": {
6
+ "development": {
7
+ "browser": "./dist/browser/index.js",
8
+ "default": "./src/index.ts"
9
+ },
10
+ "bun": "./src/index.ts",
11
+ "types": "./dist/browser/index.d.ts",
12
+ "browser": "./dist/browser/index.js",
13
+ "default": "./dist/node/index.js"
14
+ }
15
+ },
16
+ "main": "./dist/node/index.js",
17
+ "module": "./dist/node/index.js",
18
+ "types": "./dist/node/index.d.ts",
19
+ "scripts": {
20
+ "build": "tsdown",
21
+ "build:watch": "tsdown --watch",
22
+ "types:check": "tsc --noEmit"
23
+ },
24
+ "type": "module",
25
+ "dependencies": {
26
+ "@fragno-dev/core": "0.0.5",
27
+ "nanostores": "^1.0.1",
28
+ "openai": "^5.20.0",
29
+ "zod": "^4.0.5"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^20",
33
+ "@fragno-private/typescript-config": "0.0.1",
34
+ "@fragno-dev/unplugin-fragno": "0.0.1"
35
+ }
36
+ }
package/src/index.ts ADDED
@@ -0,0 +1,110 @@
1
+ import {
2
+ defineFragment,
3
+ defineRoute,
4
+ createFragment,
5
+ type FragnoPublicClientConfig,
6
+ type FragnoPublicConfig,
7
+ } from "@fragno-dev/core";
8
+ import OpenAI from "openai";
9
+ import { z } from "zod";
10
+ import { createClientBuilder } from "@fragno-dev/core/client";
11
+ import { chatRouteFactory } from "./server/chatno-api";
12
+ import { computed } from "nanostores";
13
+
14
+ export interface ChatnoServerConfig {
15
+ openaiApiKey: string;
16
+ model?: "gpt-5-mini" | "4o-mini" | "gpt-5-nano";
17
+ systemPrompt?: string;
18
+ }
19
+
20
+ const healthRoute = defineRoute({
21
+ method: "GET",
22
+ path: "/health",
23
+ outputSchema: z.object({
24
+ status: z.literal("ok"),
25
+ }),
26
+ handler: async (_ctx, { json }) => {
27
+ return json({ status: "ok" });
28
+ },
29
+ });
30
+
31
+ const simpleStreamRoute = defineRoute({
32
+ method: "GET",
33
+ path: "/simple-stream",
34
+ outputSchema: z.array(
35
+ z.object({
36
+ message: z.string(),
37
+ }),
38
+ ),
39
+ handler: async (_ctx, { jsonStream }) => {
40
+ return jsonStream(async (stream) => {
41
+ for (let i = 0; i < 10; i++) {
42
+ await stream.sleep(500);
43
+ await stream.write({ message: `Item ${i + 1}` });
44
+ }
45
+ });
46
+ },
47
+ });
48
+
49
+ const DEFAULT_SYSTEM_PROMPT = `You are an AI assistant integrated into a dashboard.`;
50
+
51
+ const chatnoDefinition = defineFragment<ChatnoServerConfig>("chatno")
52
+ .withDependencies((config: ChatnoServerConfig) => {
53
+ return {
54
+ openaiClient: new OpenAI({
55
+ apiKey: config.openaiApiKey,
56
+ }),
57
+ };
58
+ })
59
+ .withServices((_cfg, deps) => {
60
+ return {
61
+ getOpenAIURL: () => deps.openaiClient.baseURL,
62
+ };
63
+ });
64
+
65
+ const routes = [chatRouteFactory, healthRoute, simpleStreamRoute] as const;
66
+
67
+ // Server-side factory
68
+ export function createChatno(
69
+ chatnoConfig: ChatnoServerConfig,
70
+ fragnoConfig: FragnoPublicConfig = {},
71
+ ) {
72
+ const config = {
73
+ model: chatnoConfig.model ?? "gpt-5-nano",
74
+ systemPrompt: chatnoConfig.systemPrompt ?? DEFAULT_SYSTEM_PROMPT,
75
+ };
76
+
77
+ return createFragment(chatnoDefinition, { ...chatnoConfig, ...config }, routes, fragnoConfig);
78
+ }
79
+
80
+ // Client-side factory
81
+ export function createChatnoClient(fragnoConfig: FragnoPublicClientConfig = {}) {
82
+ const cb = createClientBuilder(chatnoDefinition, fragnoConfig, routes);
83
+
84
+ const chatStream = cb.createMutator("POST", "/chat/stream");
85
+
86
+ const aggregatedMessage = computed(chatStream.mutatorStore, ({ data }) => {
87
+ return (data ?? [])
88
+ .filter((item) => item.type === "response.output_text.delta")
89
+ .map((item) => item.delta)
90
+ .join("");
91
+ });
92
+
93
+ function sendMessage(message: string) {
94
+ chatStream.mutatorStore.mutate({
95
+ body: {
96
+ messages: [{ type: "chat", id: crypto.randomUUID(), role: "user", content: message }],
97
+ },
98
+ });
99
+ }
100
+
101
+ return {
102
+ useSendMessage: cb.createStore({
103
+ response: aggregatedMessage,
104
+ responseLoading: computed(chatStream.mutatorStore, ({ loading }) => loading),
105
+ sendMessage,
106
+ }),
107
+ };
108
+ }
109
+
110
+ export type { FragnoRouteConfig } from "@fragno-dev/core/api";
@@ -0,0 +1,18 @@
1
+ export interface MessageService {
2
+ setData: (messageKey: string, message: string) => Promise<void>;
3
+ getData: (messageKey: string) => Promise<string | undefined>;
4
+ }
5
+
6
+ const inMemoryMessageStore: Record<string, string> = {};
7
+ export const inMemoryMessageService: MessageService = {
8
+ setData: async (messageKey: string, message: string) => {
9
+ console.log("[InMemoryMessageService] setData", messageKey);
10
+ inMemoryMessageStore[messageKey] = message;
11
+ },
12
+ getData: async (messageKey: string) => {
13
+ console.log("[InMemoryMessageService] getData", messageKey);
14
+ return inMemoryMessageStore[messageKey];
15
+ },
16
+ };
17
+
18
+ inMemoryMessageService.setData("default", "Hello World");
@@ -0,0 +1,160 @@
1
+ import OpenAI from "openai";
2
+ import { z } from "zod";
3
+ import type {
4
+ EasyInputMessage,
5
+ ResponseFunctionToolCall,
6
+ ResponseInputItem,
7
+ } from "openai/resources/responses/responses.mjs";
8
+ import { defineRoute, defineRoutes } from "@fragno-dev/core";
9
+
10
+ export const ChatMessageSchema = z.object({
11
+ type: z.literal("chat"),
12
+ id: z.string(),
13
+ role: z.enum(["user", "assistant"]),
14
+ content: z.string(),
15
+ });
16
+
17
+ export const FunctionCallMessageSchema = z.object({
18
+ type: z.literal("functionCall"),
19
+ id: z.string(),
20
+ functionCallId: z.string(),
21
+ name: z.string(),
22
+ arguments: z.string(),
23
+ });
24
+
25
+ export const FunctionCallOutputMessageSchema = z.object({
26
+ type: z.literal("functionCallOutput"),
27
+ id: z.string(),
28
+ functionCallId: z.string(),
29
+ output: z.string(),
30
+ status: z.enum(["inProgress", "completed", "incomplete"]),
31
+ });
32
+
33
+ export const InputMessageSchema = z.discriminatedUnion("type", [
34
+ ChatMessageSchema,
35
+ FunctionCallMessageSchema,
36
+ FunctionCallOutputMessageSchema,
37
+ ]);
38
+
39
+ // Request schema with unified messages array
40
+ export const ChatStreamRequestSchema = z.object({
41
+ messages: z.array(InputMessageSchema),
42
+ });
43
+
44
+ export const ResponseTextDeltaEventSchema = z.object({
45
+ type: z.literal("response.output_text.delta"),
46
+ content_index: z.number(),
47
+ delta: z.string(),
48
+ item_id: z.string(),
49
+ output_index: z.number(),
50
+ sequence_number: z.number(),
51
+ });
52
+
53
+ export const ResponseTextDoneEventSchema = z.object({
54
+ type: z.literal("response.output_text.done"),
55
+ content_index: z.number(),
56
+ item_id: z.string(),
57
+ output_index: z.number(),
58
+ sequence_number: z.number(),
59
+ text: z.string(),
60
+ });
61
+
62
+ export const ResponseEventSchema = z.discriminatedUnion("type", [
63
+ ResponseTextDeltaEventSchema,
64
+ ResponseTextDoneEventSchema,
65
+ ]);
66
+
67
+ type ChatRouteConfig = {
68
+ model: "gpt-5-mini" | "4o-mini";
69
+ systemPrompt: string;
70
+ };
71
+
72
+ type ChatRouteDeps = {
73
+ openaiClient: OpenAI;
74
+ };
75
+
76
+ export const chatRouteFactory = defineRoutes<ChatRouteConfig, ChatRouteDeps>().create(
77
+ ({ config, deps }) => {
78
+ const { openaiClient } = deps;
79
+ const { model, systemPrompt } = config;
80
+
81
+ return [
82
+ defineRoute({
83
+ method: "POST",
84
+ path: "/chat/stream",
85
+ inputSchema: ChatStreamRequestSchema,
86
+ outputSchema: z.array(ResponseEventSchema),
87
+ handler: async ({ input }, { jsonStream }) => {
88
+ const { messages } = await input.valid();
89
+
90
+ const openAIMessages: ResponseInputItem[] = messages.map((message) => {
91
+ if (message.type === "chat") {
92
+ return {
93
+ role: message.role,
94
+ content: message.content,
95
+ } satisfies EasyInputMessage;
96
+ }
97
+
98
+ if (message.type === "functionCall") {
99
+ return {
100
+ type: "function_call",
101
+ id: message.id,
102
+ call_id: message.functionCallId,
103
+ name: message.name,
104
+ arguments: message.arguments,
105
+ } satisfies ResponseFunctionToolCall;
106
+ }
107
+
108
+ if (message.type === "functionCallOutput") {
109
+ return {
110
+ type: "function_call_output",
111
+ call_id: message.functionCallId,
112
+ output: message.output,
113
+ status:
114
+ message.status === "inProgress"
115
+ ? "in_progress"
116
+ : message.status === "completed"
117
+ ? "completed"
118
+ : "incomplete",
119
+ } satisfies ResponseInputItem.FunctionCallOutput;
120
+ }
121
+
122
+ throw new Error("unreachable");
123
+ });
124
+
125
+ try {
126
+ const eventStream = await openaiClient.responses.create({
127
+ model,
128
+ input: [
129
+ {
130
+ role: "system",
131
+ content: systemPrompt,
132
+ },
133
+ ...openAIMessages,
134
+ ],
135
+ // tools: [],
136
+ // tool_choice: "auto",
137
+ stream: true,
138
+ });
139
+
140
+ return jsonStream(async (stream) => {
141
+ for await (const event of eventStream) {
142
+ if (
143
+ event.type !== "response.output_text.delta" &&
144
+ event.type !== "response.output_text.done"
145
+ ) {
146
+ continue;
147
+ }
148
+
149
+ await stream.write(event);
150
+ }
151
+ });
152
+ } catch (error) {
153
+ console.error("OpenAI API error:", error);
154
+ throw error;
155
+ }
156
+ },
157
+ }),
158
+ ];
159
+ },
160
+ );
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "@fragno-private/typescript-config/tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": ".",
6
+ "composite": true
7
+ },
8
+ "include": ["src"],
9
+ "exclude": ["node_modules", "dist"]
10
+ }
@@ -0,0 +1,26 @@
1
+ import { defineConfig } from "tsdown";
2
+ import unpluginFragno from "@fragno-dev/unplugin-fragno/rollup";
3
+
4
+ export default defineConfig([
5
+ {
6
+ ignoreWatch: ["./dist"],
7
+ entry: "./src/index.ts",
8
+ dts: {
9
+ sourcemap: true,
10
+ },
11
+ platform: "browser",
12
+ outDir: "./dist/browser",
13
+ plugins: [unpluginFragno({ platform: "browser" })],
14
+ },
15
+ {
16
+ ignoreWatch: ["./dist"],
17
+ entry: "./src/index.ts",
18
+ dts: {
19
+ sourcemap: true,
20
+ },
21
+ outDir: "./dist/node",
22
+ // This plugin can be omitted, because it doesn't do anything for platform "node".
23
+ plugins: [unpluginFragno({ platform: "node" })],
24
+ unbundle: true,
25
+ },
26
+ ]);