juneau 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"createSkillSet.d.ts","sourceRoot":"","sources":["../../src/server/createSkillSet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACnC,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1E,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;AAUzD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAE,eAAoB,GAAG,QAAQ,CA8ExF"}
1
+ {"version":3,"file":"createSkillSet.d.ts","sourceRoot":"","sources":["../../src/server/createSkillSet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACnC,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1E,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;AAUzD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAE,eAAoB,GAAG,QAAQ,CAiGxF"}
@@ -0,0 +1,12 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function x(r){const s=r.map(e=>{const t=e.parts.filter(n=>n.type==="text").map(n=>n.text).join("");return{role:e.role,content:t}}),l=[];for(let e=0;e<s.length;e++){const t=s[e],n=l[l.length-1];t.role==="user"&&n?.role==="user"&&l.push({role:"assistant",content:"…"}),l.push(t)}return l.filter(e=>e.content.length>0)}function f(r){return`data: ${JSON.stringify(r)}
2
+
3
+ `}function g(r,s){return r[s]??r.en}function T(r,s={}){const l=s.language??"en",e=s.debug??!1,t=[];let n=!1,o=null;const a={};for(const[i,u]of Object.entries(r))a[i]={description:u.description,parameters:u.input,execute:async h=>{e&&console.debug(`[juneau] skill "${i}" → executing, input:`,h);const y=e?Date.now():0;t.push(f({type:"activity",id:i,title:g(u.labels.running,l),status:"running",metadata:{skill:i}}));try{const c=await u.execute(h),d=e?Date.now()-y:0;return e&&console.debug(`[juneau] skill "${i}" → done in ${d}ms, result:`,c),t.push(f({type:"activity",id:i,title:g(u.labels.done,l),status:"done",metadata:{skill:i}})),c}catch(c){const d=e?Date.now()-y:0,m=c instanceof Error?c.message:String(c);e&&console.debug(`[juneau] skill "${i}" → FAILED in ${d}ms: "${m}"`);const v=u.labels.failed??{cs:"Nepodařilo se",en:"Failed"};throw t.push(f({type:"activity",id:i,title:g(v,l),status:"failed",metadata:{skill:i}})),n=!0,o=`Tool "${i}" failed: ${m}`,c}}};return{tools:a,drainActivities(){return t.splice(0,t.length)},get hadFailure(){return n},get failureContext(){return o}}}const b=new Set(["text-delta","text"]),$=new Set(["tool-call","tool-result","tool-input-start","tool-input-delta","tool-input-available","tool-output-available","step-start","finish-step","start","finish","response-metadata","stream-start"]);async function*p(r,s,l){const e=l?.debug??!1;for await(const t of r){const n=t,o=n.type;if(e)if(o&&b.has(o)){const a=n.text??n.textDelta;console.debug(`[juneau] streamToWire chunk: ${o} ${JSON.stringify(a??"")}`)}else o==="tool-call"?console.debug(`[juneau] streamToWire chunk: tool-call "${n.toolName??n.name??"?"}"`):o==="tool-result"?console.debug("[juneau] streamToWire chunk: tool-result (ignored)"):o&&($.has(o)?console.debug(`[juneau] streamToWire chunk: ${o} (ignored)`):console.debug(`[juneau] streamToWire chunk: ${o} (unknown)`));if(o&&b.has(o)){if(s){const i=s.drainActivities();e&&i.length>0&&console.debug(`[juneau] streamToWire → draining ${i.length} activities`);for(const u of i)yield u}const a=n.text??n.textDelta;a&&(e&&console.debug(`[juneau] streamToWire → yielding text ${JSON.stringify(a)}`),yield`data: ${JSON.stringify({type:"text",text:a})}
4
+
5
+ `)}}if(s){const t=s.drainActivities();e&&t.length>0&&console.debug(`[juneau] streamToWire → draining ${t.length} activities (end of stream)`);for(const n of t)yield n}e&&console.debug("[juneau] streamToWire → end of stream, emitting done"),yield`data: ${JSON.stringify({type:"done"})}
6
+
7
+ `}async function*j(r){const{phase1:s,phase2:l,skillSet:e,debug:t=!1}=r;t&&console.debug("[juneau] withToolRecovery → phase 1 start");let n=!1;const o=s();for await(const a of p(o.fullStream,e,{debug:t}))a.includes('"type":"text"')&&(n=!0),!a.includes('"type":"done"')&&(yield a);if(t&&console.debug(`[juneau] withToolRecovery → phase 1 complete, textProduced=${n}, hadFailure=${e.hadFailure}`),e.hadFailure&&!n&&e.failureContext){t&&console.debug("[juneau] withToolRecovery → hadFailure=true, textProduced=false → triggering phase 2");const a=l(e.failureContext);yield*p(a.fullStream,void 0,{debug:t}),t&&console.debug("[juneau] withToolRecovery → phase 2 complete")}else!n&&!e.hadFailure?(t&&console.debug("[juneau] withToolRecovery → no failure, no text — emitting done (possible Gemini silent response)"),yield`data: ${JSON.stringify({type:"done"})}
8
+
9
+ `):yield`data: ${JSON.stringify({type:"done"})}
10
+
11
+ `}exports.createSkillSet=T;exports.streamToWire=p;exports.toSdkMessages=x;exports.withToolRecovery=j;
12
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../../src/server/toSdkMessages.ts","../../src/server/createSkillSet.ts","../../src/server/streamToWire.ts","../../src/server/withToolRecovery.ts"],"sourcesContent":["import type { AiMessage } from '../core/types';\r\nimport type { CoreMessage } from './types';\r\n\r\n/**\r\n * Converts Juneau's AiMessage[] to the CoreMessage[] format expected by\r\n * ai-sdk's generateText / streamText.\r\n *\r\n * - Extracts plain text from parts (joins all type: \"text\" parts)\r\n * - Replaces activity-only assistant messages with an empty assistant turn\r\n * so conversation alternation stays valid\r\n * - Injects a placeholder assistant turn between consecutive user messages\r\n * - Filters out empty turns (unless they are a required alternation filler)\r\n */\r\nexport function toSdkMessages(messages: AiMessage[]): CoreMessage[] {\r\n // First pass: map each AiMessage to a CoreMessage, replacing activity-only\r\n // assistant messages with an empty-text assistant turn.\r\n const mapped: CoreMessage[] = messages.map(msg => {\r\n const text = msg.parts\r\n .filter(p => p.type === 'text')\r\n .map(p => (p as { type: 'text'; text: string }).text)\r\n .join('');\r\n\r\n return { role: msg.role as CoreMessage['role'], content: text };\r\n });\r\n\r\n // Second pass: inject a placeholder assistant turn between consecutive user\r\n // messages as a safety net for history bugs that would cause model errors.\r\n const result: CoreMessage[] = [];\r\n for (let i = 0; i < mapped.length; i++) {\r\n const current = mapped[i];\r\n const prev = result[result.length - 1];\r\n\r\n if (current.role === 'user' && prev?.role === 'user') {\r\n result.push({ role: 'assistant', content: '…' });\r\n }\r\n\r\n result.push(current);\r\n }\r\n\r\n // Third pass: filter out empty turns, but keep assistant placeholders that\r\n // serve as alternation fillers (they have content '…' set above, so they\r\n // won't be filtered). Only drop genuinely empty content strings.\r\n return result.filter(msg => msg.content.length > 0);\r\n}\r\n","import type { ZodType } from 'zod';\nimport type { SkillDefinition, SkillSet, SkillSetOptions } from './types';\n\ntype SkillMap = Record<string, SkillDefinition<ZodType>>;\n\nfunction formatActivityEvent(event: Record<string, unknown>): string {\n return `data: ${JSON.stringify(event)}\\n\\n`;\n}\n\nfunction pickLabel(labels: { cs: string; en: string }, language: string): string {\n return (labels as Record<string, string>)[language] ?? labels.en;\n}\n\n/**\n * Takes a map of skill definitions and returns a SkillSet containing:\n * - tools: ready-made tool definitions for ai-sdk's streamText({ tools })\n * - drainActivities(): flush buffered activity wire SSE strings\n * - hadFailure / failureContext: for driving tool failure recovery\n */\nexport function createSkillSet(skills: SkillMap, options: SkillSetOptions = {}): SkillSet {\n const language = options.language ?? 'en';\n const debug = options.debug ?? false;\n const activityBuffer: string[] = [];\n let hadFailure = false;\n let failureContext: string | null = null;\n\n const tools: Record<string, unknown> = {};\n\n for (const [name, skill] of Object.entries(skills)) {\n tools[name] = {\n description: skill.description,\n parameters: skill.input,\n execute: async (input: unknown) => {\n if (debug) {\n console.debug(`[juneau] skill \"${name}\" → executing, input:`, input);\n }\n\n const startMs = debug ? Date.now() : 0;\n\n // Emit a \"running\" activity event into the buffer\n activityBuffer.push(\n formatActivityEvent({\n type: 'activity',\n id: name,\n title: pickLabel(skill.labels.running, language),\n status: 'running',\n metadata: { skill: name },\n })\n );\n\n try {\n const result = await skill.execute(input as never);\n\n const durationMs = debug ? Date.now() - startMs : 0;\n if (debug) {\n console.debug(`[juneau] skill \"${name}\" → done in ${durationMs}ms, result:`, result);\n }\n\n // Emit a \"done\" activity event\n activityBuffer.push(\n formatActivityEvent({\n type: 'activity',\n id: name,\n title: pickLabel(skill.labels.done, language),\n status: 'done',\n metadata: { skill: name },\n })\n );\n\n return result;\n } catch (err) {\n const durationMs = debug ? Date.now() - startMs : 0;\n const message = err instanceof Error ? err.message : String(err);\n\n if (debug) {\n console.debug(`[juneau] skill \"${name}\" → FAILED in ${durationMs}ms: \"${message}\"`);\n }\n\n const failedLabels = skill.labels.failed ?? { cs: 'Nepodařilo se', en: 'Failed' };\n\n // Emit a \"failed\" activity event\n activityBuffer.push(\n formatActivityEvent({\n type: 'activity',\n id: name,\n title: pickLabel(failedLabels, language),\n status: 'failed',\n metadata: { skill: name },\n })\n );\n\n hadFailure = true;\n failureContext = `Tool \"${name}\" failed: ${message}`;\n\n // Re-throw so ai-sdk knows the tool call failed\n throw err;\n }\n },\n };\n }\n\n return {\n tools,\n\n drainActivities(): string[] {\n return activityBuffer.splice(0, activityBuffer.length);\n },\n\n get hadFailure() {\n return hadFailure;\n },\n\n get failureContext() {\n return failureContext;\n },\n };\n}\n","import type { SkillSet, StreamToWireOptions } from './types';\n\n// ai-sdk v7 chunk types that carry the model's text output.\n// - 'text-delta': standard streaming text (v6 + v7)\n// - 'text': emitted by some v7 model/provider combos as a completed text chunk\n// Both may appear in the same stream; we handle either.\nconst TEXT_CHUNK_TYPES = new Set(['text-delta', 'text']);\n\n// Chunk types we recognise but intentionally ignore — activities are driven by\n// the SkillSet execute fn, not by these wire events.\nconst IGNORED_CHUNK_TYPES = new Set([\n 'tool-call',\n 'tool-result',\n 'tool-input-start',\n 'tool-input-delta',\n 'tool-input-available',\n 'tool-output-available',\n 'step-start',\n 'finish-step',\n 'start',\n 'finish',\n 'response-metadata',\n 'stream-start',\n]);\n\n/**\n * Converts ai-sdk's fullStream AsyncIterable into Juneau wire SSE strings.\n *\n * Handles:\n * - All text chunk types used by ai-sdk v7 (text-delta, text) incl. v6 textDelta fallback\n * - Flushing skillSet.drainActivities() before each text chunk so activities\n * always appear before the text they precede\n * - A final drain after the loop ends (catches last-tool-call activities when\n * no text follows the tool result — the Gemini silent-response scenario)\n * - Emitting the done event when the stream finishes\n * - Structured debug logging of every chunk when options.debug is true\n */\nexport async function* streamToWire(\n fullStream: AsyncIterable<unknown>,\n skillSet?: SkillSet,\n options?: StreamToWireOptions\n): AsyncIterable<string> {\n const debug = options?.debug ?? false;\n\n for await (const chunk of fullStream) {\n const c = chunk as Record<string, unknown>;\n const type = c.type as string | undefined;\n\n if (debug) {\n if (type && TEXT_CHUNK_TYPES.has(type)) {\n const text = (c.text ?? c.textDelta) as string | undefined;\n console.debug(`[juneau] streamToWire chunk: ${type} ${JSON.stringify(text ?? '')}`);\n } else if (type === 'tool-call') {\n console.debug(`[juneau] streamToWire chunk: tool-call \"${c.toolName ?? c.name ?? '?'}\"`);\n } else if (type === 'tool-result') {\n console.debug(`[juneau] streamToWire chunk: tool-result (ignored)`);\n } else if (type) {\n if (IGNORED_CHUNK_TYPES.has(type)) {\n console.debug(`[juneau] streamToWire chunk: ${type} (ignored)`);\n } else {\n console.debug(`[juneau] streamToWire chunk: ${type} (unknown)`);\n }\n }\n }\n\n if (type && TEXT_CHUNK_TYPES.has(type)) {\n // Flush any buffered activity events before emitting text so the UI\n // shows running/done indicators before the model's reply appears.\n if (skillSet) {\n const activities = skillSet.drainActivities();\n if (debug && activities.length > 0) {\n console.debug(`[juneau] streamToWire → draining ${activities.length} activities`);\n }\n for (const event of activities) {\n yield event;\n }\n }\n\n // ai-sdk v7 uses `text`, v6 used `textDelta` — support both\n const text = (c.text ?? c.textDelta) as string | undefined;\n if (text) {\n if (debug) {\n console.debug(`[juneau] streamToWire → yielding text ${JSON.stringify(text)}`);\n }\n yield `data: ${JSON.stringify({ type: 'text', text })}\\n\\n`;\n }\n }\n\n // All other chunk types are intentionally silent — the SkillSet execute fn\n // emits activity events into its buffer which we drain above.\n }\n\n // Final drain — catches activity events from the last tool call when no\n // text-delta followed (Gemini sometimes calls a tool successfully but emits\n // the reply text in a subsequent step that never arrives as text-delta here).\n if (skillSet) {\n const remaining = skillSet.drainActivities();\n if (debug && remaining.length > 0) {\n console.debug(`[juneau] streamToWire → draining ${remaining.length} activities (end of stream)`);\n }\n for (const event of remaining) {\n yield event;\n }\n }\n\n if (debug) {\n console.debug('[juneau] streamToWire → end of stream, emitting done');\n }\n\n yield `data: ${JSON.stringify({ type: 'done' })}\\n\\n`;\n}\n","import type { ToolRecoveryOptions } from './types';\nimport { streamToWire } from './streamToWire';\n\n/**\n * Encapsulates the two-phase streaming pattern needed for Gemini 2.5 Flash\n * tool call scenarios where the model either fails or silently succeeds without\n * emitting any text in the same stream pass.\n *\n * Phase 1: Stream with tools, maxSteps: 2 (or higher). Collect whether any\n * text was produced and whether a tool failure occurred.\n *\n * Phase 2 triggers when a tool failed AND no text was produced:\n * - Injects failureContext as an assistant message so the model is forced to\n * write a recovery text response (no tools available in phase 2).\n *\n * Silent-success path (hadFailure=false, textProduced=false):\n * - Gemini called the tool successfully but wrote nothing. This is a known\n * Gemini 2.5 Flash behaviour when maxSteps > 1: the tool result and the\n * model's reply text arrive as separate steps, and text-delta may not be\n * emitted by the time phase 1's fullStream is exhausted. The debug logs\n * will reveal the exact chunk sequence. In this state we still emit done\n * cleanly — the consumer can decide whether to retry.\n *\n * @returns AsyncIterable<string> of Juneau wire SSE strings (activities + text + done)\n */\nexport async function* withToolRecovery(options: ToolRecoveryOptions): AsyncIterable<string> {\n const { phase1, phase2, skillSet, debug = false } = options;\n\n if (debug) {\n console.debug('[juneau] withToolRecovery → phase 1 start');\n }\n\n let producedText = false;\n\n const result1 = phase1();\n\n for await (const chunk of streamToWire(result1.fullStream, skillSet, { debug })) {\n // Track whether any text reached the wire (done event doesn't count)\n if (chunk.includes('\"type\":\"text\"')) {\n producedText = true;\n }\n\n // Hold back the done event — we may need to continue with phase 2\n if (chunk.includes('\"type\":\"done\"')) {\n continue;\n }\n\n yield chunk;\n }\n\n if (debug) {\n console.debug(\n `[juneau] withToolRecovery → phase 1 complete, textProduced=${producedText}, hadFailure=${skillSet.hadFailure}`\n );\n }\n\n if (skillSet.hadFailure && !producedText && skillSet.failureContext) {\n // Tool failed and model wrote nothing — run phase 2 to force a text response\n if (debug) {\n console.debug('[juneau] withToolRecovery → hadFailure=true, textProduced=false → triggering phase 2');\n }\n\n const result2 = phase2(skillSet.failureContext);\n\n // Phase 2 has no skillSet — plain text response, no tool calls expected\n yield* streamToWire(result2.fullStream, undefined, { debug });\n\n if (debug) {\n console.debug('[juneau] withToolRecovery → phase 2 complete');\n }\n } else if (!producedText && !skillSet.hadFailure) {\n // Tool succeeded but model emitted no text — Gemini silent-response scenario.\n // Log clearly so the consumer can see this in debug output and diagnose\n // whether text arrived in a different chunk type or was simply not generated.\n if (debug) {\n console.debug(\n '[juneau] withToolRecovery → no failure, no text — emitting done (possible Gemini silent response)'\n );\n }\n\n yield `data: ${JSON.stringify({ type: 'done' })}\\n\\n`;\n } else {\n // Normal success — text was produced, emit done\n yield `data: ${JSON.stringify({ type: 'done' })}\\n\\n`;\n }\n}\n"],"names":["toSdkMessages","messages","mapped","msg","text","p","result","i","current","prev","formatActivityEvent","event","pickLabel","labels","language","createSkillSet","skills","options","debug","activityBuffer","hadFailure","failureContext","tools","name","skill","input","startMs","durationMs","err","message","failedLabels","TEXT_CHUNK_TYPES","IGNORED_CHUNK_TYPES","streamToWire","fullStream","skillSet","chunk","c","type","activities","remaining","withToolRecovery","phase1","phase2","producedText","result1","result2"],"mappings":"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"}
@@ -2,5 +2,5 @@ export { toSdkMessages } from './toSdkMessages';
2
2
  export { createSkillSet } from './createSkillSet';
3
3
  export { streamToWire } from './streamToWire';
4
4
  export { withToolRecovery } from './withToolRecovery';
5
- export type { SkillSet, SkillDefinition, SkillSetOptions, ToolRecoveryOptions, CoreMessage } from './types';
5
+ export type { SkillSet, SkillDefinition, SkillSetOptions, ToolRecoveryOptions, StreamToWireOptions, CoreMessage } from './types';
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,eAAe,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,eAAe,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC"}
@@ -1,116 +1,153 @@
1
- function h(a) {
2
- const n = a.map((t) => {
3
- const i = t.parts.filter((r) => r.type === "text").map((r) => r.text).join("");
4
- return { role: t.role, content: i };
5
- }), e = [];
6
- for (let t = 0; t < n.length; t++) {
7
- const i = n[t], r = e[e.length - 1];
8
- i.role === "user" && r?.role === "user" && e.push({ role: "assistant", content: "…" }), e.push(i);
1
+ function $(r) {
2
+ const s = r.map((e) => {
3
+ const t = e.parts.filter((n) => n.type === "text").map((n) => n.text).join("");
4
+ return { role: e.role, content: t };
5
+ }), l = [];
6
+ for (let e = 0; e < s.length; e++) {
7
+ const t = s[e], n = l[l.length - 1];
8
+ t.role === "user" && n?.role === "user" && l.push({ role: "assistant", content: "…" }), l.push(t);
9
9
  }
10
- return e.filter((t) => t.content.length > 0);
10
+ return l.filter((e) => e.content.length > 0);
11
11
  }
12
- function u(a) {
13
- return `data: ${JSON.stringify(a)}
12
+ function f(r) {
13
+ return `data: ${JSON.stringify(r)}
14
14
 
15
15
  `;
16
16
  }
17
- function f(a, n) {
18
- return a[n] ?? a.en;
17
+ function g(r, s) {
18
+ return r[s] ?? r.en;
19
19
  }
20
- function g(a, n = {}) {
21
- const e = n.language ?? "en", t = [];
22
- let i = !1, r = null;
23
- const s = {};
24
- for (const [o, l] of Object.entries(a))
25
- s[o] = {
26
- description: l.description,
27
- parameters: l.input,
20
+ function T(r, s = {}) {
21
+ const l = s.language ?? "en", e = s.debug ?? !1, t = [];
22
+ let n = !1, o = null;
23
+ const a = {};
24
+ for (const [i, u] of Object.entries(r))
25
+ a[i] = {
26
+ description: u.description,
27
+ parameters: u.input,
28
28
  execute: async (p) => {
29
+ e && console.debug(`[juneau] skill "${i}" → executing, input:`, p);
30
+ const h = e ? Date.now() : 0;
29
31
  t.push(
30
- u({
32
+ f({
31
33
  type: "activity",
32
- id: o,
33
- title: f(l.labels.running, e),
34
+ id: i,
35
+ title: g(u.labels.running, l),
34
36
  status: "running",
35
- metadata: { skill: o }
37
+ metadata: { skill: i }
36
38
  })
37
39
  );
38
40
  try {
39
- const c = await l.execute(p);
40
- return t.push(
41
- u({
41
+ const c = await u.execute(p), d = e ? Date.now() - h : 0;
42
+ return e && console.debug(`[juneau] skill "${i}" → done in ${d}ms, result:`, c), t.push(
43
+ f({
42
44
  type: "activity",
43
- id: o,
44
- title: f(l.labels.done, e),
45
+ id: i,
46
+ title: g(u.labels.done, l),
45
47
  status: "done",
46
- metadata: { skill: o }
48
+ metadata: { skill: i }
47
49
  })
48
50
  ), c;
49
51
  } catch (c) {
50
- const y = l.labels.failed ?? { cs: "Nepodařilo se", en: "Failed" };
52
+ const d = e ? Date.now() - h : 0, y = c instanceof Error ? c.message : String(c);
53
+ e && console.debug(`[juneau] skill "${i}" → FAILED in ${d}ms: "${y}"`);
54
+ const x = u.labels.failed ?? { cs: "Nepodařilo se", en: "Failed" };
51
55
  throw t.push(
52
- u({
56
+ f({
53
57
  type: "activity",
54
- id: o,
55
- title: f(y, e),
58
+ id: i,
59
+ title: g(x, l),
56
60
  status: "failed",
57
- metadata: { skill: o }
61
+ metadata: { skill: i }
58
62
  })
59
- ), i = !0, r = `Tool "${o}" failed: ${c instanceof Error ? c.message : String(c)}`, c;
63
+ ), n = !0, o = `Tool "${i}" failed: ${y}`, c;
60
64
  }
61
65
  }
62
66
  };
63
67
  return {
64
- tools: s,
68
+ tools: a,
65
69
  drainActivities() {
66
70
  return t.splice(0, t.length);
67
71
  },
68
72
  get hadFailure() {
69
- return i;
73
+ return n;
70
74
  },
71
75
  get failureContext() {
72
- return r;
76
+ return o;
73
77
  }
74
78
  };
75
79
  }
76
- async function* d(a, n) {
77
- for await (const e of a) {
78
- const t = e;
79
- if (t.type === "text-delta") {
80
- if (n)
81
- for (const r of n.drainActivities())
82
- yield r;
83
- const i = t.text ?? t.textDelta;
84
- i && (yield `data: ${JSON.stringify({ type: "text", text: i })}
80
+ const m = /* @__PURE__ */ new Set(["text-delta", "text"]), v = /* @__PURE__ */ new Set([
81
+ "tool-call",
82
+ "tool-result",
83
+ "tool-input-start",
84
+ "tool-input-delta",
85
+ "tool-input-available",
86
+ "tool-output-available",
87
+ "step-start",
88
+ "finish-step",
89
+ "start",
90
+ "finish",
91
+ "response-metadata",
92
+ "stream-start"
93
+ ]);
94
+ async function* b(r, s, l) {
95
+ const e = l?.debug ?? !1;
96
+ for await (const t of r) {
97
+ const n = t, o = n.type;
98
+ if (e)
99
+ if (o && m.has(o)) {
100
+ const a = n.text ?? n.textDelta;
101
+ console.debug(`[juneau] streamToWire chunk: ${o} ${JSON.stringify(a ?? "")}`);
102
+ } else o === "tool-call" ? console.debug(`[juneau] streamToWire chunk: tool-call "${n.toolName ?? n.name ?? "?"}"`) : o === "tool-result" ? console.debug("[juneau] streamToWire chunk: tool-result (ignored)") : o && (v.has(o) ? console.debug(`[juneau] streamToWire chunk: ${o} (ignored)`) : console.debug(`[juneau] streamToWire chunk: ${o} (unknown)`));
103
+ if (o && m.has(o)) {
104
+ if (s) {
105
+ const i = s.drainActivities();
106
+ e && i.length > 0 && console.debug(`[juneau] streamToWire → draining ${i.length} activities`);
107
+ for (const u of i)
108
+ yield u;
109
+ }
110
+ const a = n.text ?? n.textDelta;
111
+ a && (e && console.debug(`[juneau] streamToWire → yielding text ${JSON.stringify(a)}`), yield `data: ${JSON.stringify({ type: "text", text: a })}
85
112
 
86
113
  `);
87
114
  }
88
115
  }
89
- if (n)
90
- for (const e of n.drainActivities())
91
- yield e;
92
- yield `data: ${JSON.stringify({ type: "done" })}
116
+ if (s) {
117
+ const t = s.drainActivities();
118
+ e && t.length > 0 && console.debug(`[juneau] streamToWire → draining ${t.length} activities (end of stream)`);
119
+ for (const n of t)
120
+ yield n;
121
+ }
122
+ e && console.debug("[juneau] streamToWire → end of stream, emitting done"), yield `data: ${JSON.stringify({ type: "done" })}
93
123
 
94
124
  `;
95
125
  }
96
- async function* x(a) {
97
- const { phase1: n, phase2: e, skillSet: t } = a;
98
- let i = !1;
99
- const r = n();
100
- for await (const s of d(r.fullStream, t))
101
- s.includes('"type":"text"') && (i = !0), !s.includes('"type":"done"') && (yield s);
102
- if (t.hadFailure && !i && t.failureContext) {
103
- const s = e(t.failureContext);
104
- yield* d(s.fullStream);
105
- } else
106
- yield `data: ${JSON.stringify({ type: "done" })}
126
+ async function* j(r) {
127
+ const { phase1: s, phase2: l, skillSet: e, debug: t = !1 } = r;
128
+ t && console.debug("[juneau] withToolRecovery → phase 1 start");
129
+ let n = !1;
130
+ const o = s();
131
+ for await (const a of b(o.fullStream, e, { debug: t }))
132
+ a.includes('"type":"text"') && (n = !0), !a.includes('"type":"done"') && (yield a);
133
+ if (t && console.debug(
134
+ `[juneau] withToolRecovery → phase 1 complete, textProduced=${n}, hadFailure=${e.hadFailure}`
135
+ ), e.hadFailure && !n && e.failureContext) {
136
+ t && console.debug("[juneau] withToolRecovery → hadFailure=true, textProduced=false → triggering phase 2");
137
+ const a = l(e.failureContext);
138
+ yield* b(a.fullStream, void 0, { debug: t }), t && console.debug("[juneau] withToolRecovery → phase 2 complete");
139
+ } else !n && !e.hadFailure ? (t && console.debug(
140
+ "[juneau] withToolRecovery → no failure, no text — emitting done (possible Gemini silent response)"
141
+ ), yield `data: ${JSON.stringify({ type: "done" })}
142
+
143
+ `) : yield `data: ${JSON.stringify({ type: "done" })}
107
144
 
108
145
  `;
109
146
  }
110
147
  export {
111
- g as createSkillSet,
112
- d as streamToWire,
113
- h as toSdkMessages,
114
- x as withToolRecovery
148
+ T as createSkillSet,
149
+ b as streamToWire,
150
+ $ as toSdkMessages,
151
+ j as withToolRecovery
115
152
  };
116
153
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/server/toSdkMessages.ts","../../src/server/createSkillSet.ts","../../src/server/streamToWire.ts","../../src/server/withToolRecovery.ts"],"sourcesContent":["import type { AiMessage } from '../core/types';\nimport type { CoreMessage } from './types';\n\n/**\n * Converts Juneau's AiMessage[] to the CoreMessage[] format expected by\n * ai-sdk's generateText / streamText.\n *\n * - Extracts plain text from parts (joins all type: \"text\" parts)\n * - Replaces activity-only assistant messages with an empty assistant turn\n * so conversation alternation stays valid\n * - Injects a placeholder assistant turn between consecutive user messages\n * - Filters out empty turns (unless they are a required alternation filler)\n */\nexport function toSdkMessages(messages: AiMessage[]): CoreMessage[] {\n // First pass: map each AiMessage to a CoreMessage, replacing activity-only\n // assistant messages with an empty-text assistant turn.\n const mapped: CoreMessage[] = messages.map(msg => {\n const text = msg.parts\n .filter(p => p.type === 'text')\n .map(p => (p as { type: 'text'; text: string }).text)\n .join('');\n\n return { role: msg.role as CoreMessage['role'], content: text };\n });\n\n // Second pass: inject a placeholder assistant turn between consecutive user\n // messages as a safety net for history bugs that would cause model errors.\n const result: CoreMessage[] = [];\n for (let i = 0; i < mapped.length; i++) {\n const current = mapped[i];\n const prev = result[result.length - 1];\n\n if (current.role === 'user' && prev?.role === 'user') {\n result.push({ role: 'assistant', content: '…' });\n }\n\n result.push(current);\n }\n\n // Third pass: filter out empty turns, but keep assistant placeholders that\n // serve as alternation fillers (they have content '…' set above, so they\n // won't be filtered). Only drop genuinely empty content strings.\n return result.filter(msg => msg.content.length > 0);\n}\n","import type { ZodType } from 'zod';\nimport type { SkillDefinition, SkillSet, SkillSetOptions } from './types';\n\ntype SkillMap = Record<string, SkillDefinition<ZodType>>;\n\nfunction formatActivityEvent(event: Record<string, unknown>): string {\n return `data: ${JSON.stringify(event)}\\n\\n`;\n}\n\nfunction pickLabel(labels: { cs: string; en: string }, language: string): string {\n return (labels as Record<string, string>)[language] ?? labels.en;\n}\n\n/**\n * Takes a map of skill definitions and returns a SkillSet containing:\n * - tools: ready-made tool definitions for ai-sdk's streamText({ tools })\n * - drainActivities(): flush buffered activity wire SSE strings\n * - hadFailure / failureContext: for driving tool failure recovery\n */\nexport function createSkillSet(skills: SkillMap, options: SkillSetOptions = {}): SkillSet {\n const language = options.language ?? 'en';\n const activityBuffer: string[] = [];\n let hadFailure = false;\n let failureContext: string | null = null;\n\n const tools: Record<string, unknown> = {};\n\n for (const [name, skill] of Object.entries(skills)) {\n tools[name] = {\n description: skill.description,\n parameters: skill.input,\n execute: async (input: unknown) => {\n // Emit a \"running\" activity event into the buffer\n activityBuffer.push(\n formatActivityEvent({\n type: 'activity',\n id: name,\n title: pickLabel(skill.labels.running, language),\n status: 'running',\n metadata: { skill: name },\n })\n );\n\n try {\n const result = await skill.execute(input as never);\n\n // Emit a \"done\" activity event\n activityBuffer.push(\n formatActivityEvent({\n type: 'activity',\n id: name,\n title: pickLabel(skill.labels.done, language),\n status: 'done',\n metadata: { skill: name },\n })\n );\n\n return result;\n } catch (err) {\n const failedLabels = skill.labels.failed ?? { cs: 'Nepodařilo se', en: 'Failed' };\n\n // Emit a \"failed\" activity event\n activityBuffer.push(\n formatActivityEvent({\n type: 'activity',\n id: name,\n title: pickLabel(failedLabels, language),\n status: 'failed',\n metadata: { skill: name },\n })\n );\n\n hadFailure = true;\n failureContext = `Tool \"${name}\" failed: ${err instanceof Error ? err.message : String(err)}`;\n\n // Re-throw so ai-sdk knows the tool call failed\n throw err;\n }\n },\n };\n }\n\n return {\n tools,\n\n drainActivities(): string[] {\n return activityBuffer.splice(0, activityBuffer.length);\n },\n\n get hadFailure() {\n return hadFailure;\n },\n\n get failureContext() {\n return failureContext;\n },\n };\n}\n","import type { SkillSet } from './types';\n\n/**\n * Converts ai-sdk's fullStream AsyncIterable into Juneau wire SSE strings.\n *\n * Handles:\n * - ai-sdk v7 chunk property rename (chunk.text) with fallback to v6 (chunk.textDelta)\n * - Flushing skillSet.drainActivities() before each text chunk\n * - A final drain after the loop ends (catches last tool call activities)\n * - Emitting the done event when the stream finishes\n */\nexport async function* streamToWire(\n fullStream: AsyncIterable<unknown>,\n skillSet?: SkillSet\n): AsyncIterable<string> {\n for await (const chunk of fullStream) {\n const c = chunk as Record<string, unknown>;\n\n if (c.type === 'text-delta') {\n // Flush any buffered activity events before emitting text\n if (skillSet) {\n for (const event of skillSet.drainActivities()) {\n yield event;\n }\n }\n\n // ai-sdk v7 uses `text`, v6 used `textDelta`\n const text = (c.text ?? c.textDelta) as string | undefined;\n if (text) {\n yield `data: ${JSON.stringify({ type: 'text', text })}\\n\\n`;\n }\n }\n\n // tool-call and tool-result chunks are handled silently — the SkillSet\n // execute fn emits activity events into its buffer which we drain above.\n }\n\n // Final drain — catches activity events from the last tool call that fired\n // after the last text-delta (or when there were no text deltas at all).\n if (skillSet) {\n for (const event of skillSet.drainActivities()) {\n yield event;\n }\n }\n\n yield `data: ${JSON.stringify({ type: 'done' })}\\n\\n`;\n}\n","import type { ToolRecoveryOptions } from './types';\nimport { streamToWire } from './streamToWire';\n\n/**\n * Encapsulates the two-phase streaming pattern needed when a tool fails and\n * the model retries the tool instead of writing text (observed with Gemini 2.5 Flash).\n *\n * Phase 1: Stream with tools, maxSteps: 1. Collect whether any text was produced.\n * Phase 2: If a tool failed and no text was produced, call the model again without\n * tools, injecting the failure context as an assistant message so the model\n * is forced to write a text response.\n *\n * @returns AsyncIterable<string> of Juneau wire SSE strings (activities + text + done)\n */\nexport async function* withToolRecovery(options: ToolRecoveryOptions): AsyncIterable<string> {\n const { phase1, phase2, skillSet } = options;\n\n let producedText = false;\n\n const result1 = phase1();\n\n for await (const chunk of streamToWire(result1.fullStream, skillSet)) {\n // Track whether any text reached the wire (done event doesn't count)\n if (chunk.includes('\"type\":\"text\"')) {\n producedText = true;\n }\n\n // Don't yield the done event yet — we may need to continue with phase 2\n if (chunk.includes('\"type\":\"done\"')) {\n continue;\n }\n\n yield chunk;\n }\n\n // If a tool failed and the model produced no text, run phase 2 to force a\n // text response without tools available.\n if (skillSet.hadFailure && !producedText && skillSet.failureContext) {\n const result2 = phase2(skillSet.failureContext);\n\n // Phase 2 has no skillSet — plain text stream, no tool calls\n yield* streamToWire(result2.fullStream);\n } else {\n // All good — emit done\n yield `data: ${JSON.stringify({ type: 'done' })}\\n\\n`;\n }\n}\n"],"names":["toSdkMessages","messages","mapped","msg","text","p","result","i","current","prev","formatActivityEvent","event","pickLabel","labels","language","createSkillSet","skills","options","activityBuffer","hadFailure","failureContext","tools","name","skill","input","err","failedLabels","streamToWire","fullStream","skillSet","chunk","c","withToolRecovery","phase1","phase2","producedText","result1","result2"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../src/server/toSdkMessages.ts","../../src/server/createSkillSet.ts","../../src/server/streamToWire.ts","../../src/server/withToolRecovery.ts"],"sourcesContent":["import type { AiMessage } from '../core/types';\r\nimport type { CoreMessage } from './types';\r\n\r\n/**\r\n * Converts Juneau's AiMessage[] to the CoreMessage[] format expected by\r\n * ai-sdk's generateText / streamText.\r\n *\r\n * - Extracts plain text from parts (joins all type: \"text\" parts)\r\n * - Replaces activity-only assistant messages with an empty assistant turn\r\n * so conversation alternation stays valid\r\n * - Injects a placeholder assistant turn between consecutive user messages\r\n * - Filters out empty turns (unless they are a required alternation filler)\r\n */\r\nexport function toSdkMessages(messages: AiMessage[]): CoreMessage[] {\r\n // First pass: map each AiMessage to a CoreMessage, replacing activity-only\r\n // assistant messages with an empty-text assistant turn.\r\n const mapped: CoreMessage[] = messages.map(msg => {\r\n const text = msg.parts\r\n .filter(p => p.type === 'text')\r\n .map(p => (p as { type: 'text'; text: string }).text)\r\n .join('');\r\n\r\n return { role: msg.role as CoreMessage['role'], content: text };\r\n });\r\n\r\n // Second pass: inject a placeholder assistant turn between consecutive user\r\n // messages as a safety net for history bugs that would cause model errors.\r\n const result: CoreMessage[] = [];\r\n for (let i = 0; i < mapped.length; i++) {\r\n const current = mapped[i];\r\n const prev = result[result.length - 1];\r\n\r\n if (current.role === 'user' && prev?.role === 'user') {\r\n result.push({ role: 'assistant', content: '…' });\r\n }\r\n\r\n result.push(current);\r\n }\r\n\r\n // Third pass: filter out empty turns, but keep assistant placeholders that\r\n // serve as alternation fillers (they have content '…' set above, so they\r\n // won't be filtered). Only drop genuinely empty content strings.\r\n return result.filter(msg => msg.content.length > 0);\r\n}\r\n","import type { ZodType } from 'zod';\nimport type { SkillDefinition, SkillSet, SkillSetOptions } from './types';\n\ntype SkillMap = Record<string, SkillDefinition<ZodType>>;\n\nfunction formatActivityEvent(event: Record<string, unknown>): string {\n return `data: ${JSON.stringify(event)}\\n\\n`;\n}\n\nfunction pickLabel(labels: { cs: string; en: string }, language: string): string {\n return (labels as Record<string, string>)[language] ?? labels.en;\n}\n\n/**\n * Takes a map of skill definitions and returns a SkillSet containing:\n * - tools: ready-made tool definitions for ai-sdk's streamText({ tools })\n * - drainActivities(): flush buffered activity wire SSE strings\n * - hadFailure / failureContext: for driving tool failure recovery\n */\nexport function createSkillSet(skills: SkillMap, options: SkillSetOptions = {}): SkillSet {\n const language = options.language ?? 'en';\n const debug = options.debug ?? false;\n const activityBuffer: string[] = [];\n let hadFailure = false;\n let failureContext: string | null = null;\n\n const tools: Record<string, unknown> = {};\n\n for (const [name, skill] of Object.entries(skills)) {\n tools[name] = {\n description: skill.description,\n parameters: skill.input,\n execute: async (input: unknown) => {\n if (debug) {\n console.debug(`[juneau] skill \"${name}\" → executing, input:`, input);\n }\n\n const startMs = debug ? Date.now() : 0;\n\n // Emit a \"running\" activity event into the buffer\n activityBuffer.push(\n formatActivityEvent({\n type: 'activity',\n id: name,\n title: pickLabel(skill.labels.running, language),\n status: 'running',\n metadata: { skill: name },\n })\n );\n\n try {\n const result = await skill.execute(input as never);\n\n const durationMs = debug ? Date.now() - startMs : 0;\n if (debug) {\n console.debug(`[juneau] skill \"${name}\" → done in ${durationMs}ms, result:`, result);\n }\n\n // Emit a \"done\" activity event\n activityBuffer.push(\n formatActivityEvent({\n type: 'activity',\n id: name,\n title: pickLabel(skill.labels.done, language),\n status: 'done',\n metadata: { skill: name },\n })\n );\n\n return result;\n } catch (err) {\n const durationMs = debug ? Date.now() - startMs : 0;\n const message = err instanceof Error ? err.message : String(err);\n\n if (debug) {\n console.debug(`[juneau] skill \"${name}\" → FAILED in ${durationMs}ms: \"${message}\"`);\n }\n\n const failedLabels = skill.labels.failed ?? { cs: 'Nepodařilo se', en: 'Failed' };\n\n // Emit a \"failed\" activity event\n activityBuffer.push(\n formatActivityEvent({\n type: 'activity',\n id: name,\n title: pickLabel(failedLabels, language),\n status: 'failed',\n metadata: { skill: name },\n })\n );\n\n hadFailure = true;\n failureContext = `Tool \"${name}\" failed: ${message}`;\n\n // Re-throw so ai-sdk knows the tool call failed\n throw err;\n }\n },\n };\n }\n\n return {\n tools,\n\n drainActivities(): string[] {\n return activityBuffer.splice(0, activityBuffer.length);\n },\n\n get hadFailure() {\n return hadFailure;\n },\n\n get failureContext() {\n return failureContext;\n },\n };\n}\n","import type { SkillSet, StreamToWireOptions } from './types';\n\n// ai-sdk v7 chunk types that carry the model's text output.\n// - 'text-delta': standard streaming text (v6 + v7)\n// - 'text': emitted by some v7 model/provider combos as a completed text chunk\n// Both may appear in the same stream; we handle either.\nconst TEXT_CHUNK_TYPES = new Set(['text-delta', 'text']);\n\n// Chunk types we recognise but intentionally ignore — activities are driven by\n// the SkillSet execute fn, not by these wire events.\nconst IGNORED_CHUNK_TYPES = new Set([\n 'tool-call',\n 'tool-result',\n 'tool-input-start',\n 'tool-input-delta',\n 'tool-input-available',\n 'tool-output-available',\n 'step-start',\n 'finish-step',\n 'start',\n 'finish',\n 'response-metadata',\n 'stream-start',\n]);\n\n/**\n * Converts ai-sdk's fullStream AsyncIterable into Juneau wire SSE strings.\n *\n * Handles:\n * - All text chunk types used by ai-sdk v7 (text-delta, text) incl. v6 textDelta fallback\n * - Flushing skillSet.drainActivities() before each text chunk so activities\n * always appear before the text they precede\n * - A final drain after the loop ends (catches last-tool-call activities when\n * no text follows the tool result — the Gemini silent-response scenario)\n * - Emitting the done event when the stream finishes\n * - Structured debug logging of every chunk when options.debug is true\n */\nexport async function* streamToWire(\n fullStream: AsyncIterable<unknown>,\n skillSet?: SkillSet,\n options?: StreamToWireOptions\n): AsyncIterable<string> {\n const debug = options?.debug ?? false;\n\n for await (const chunk of fullStream) {\n const c = chunk as Record<string, unknown>;\n const type = c.type as string | undefined;\n\n if (debug) {\n if (type && TEXT_CHUNK_TYPES.has(type)) {\n const text = (c.text ?? c.textDelta) as string | undefined;\n console.debug(`[juneau] streamToWire chunk: ${type} ${JSON.stringify(text ?? '')}`);\n } else if (type === 'tool-call') {\n console.debug(`[juneau] streamToWire chunk: tool-call \"${c.toolName ?? c.name ?? '?'}\"`);\n } else if (type === 'tool-result') {\n console.debug(`[juneau] streamToWire chunk: tool-result (ignored)`);\n } else if (type) {\n if (IGNORED_CHUNK_TYPES.has(type)) {\n console.debug(`[juneau] streamToWire chunk: ${type} (ignored)`);\n } else {\n console.debug(`[juneau] streamToWire chunk: ${type} (unknown)`);\n }\n }\n }\n\n if (type && TEXT_CHUNK_TYPES.has(type)) {\n // Flush any buffered activity events before emitting text so the UI\n // shows running/done indicators before the model's reply appears.\n if (skillSet) {\n const activities = skillSet.drainActivities();\n if (debug && activities.length > 0) {\n console.debug(`[juneau] streamToWire → draining ${activities.length} activities`);\n }\n for (const event of activities) {\n yield event;\n }\n }\n\n // ai-sdk v7 uses `text`, v6 used `textDelta` — support both\n const text = (c.text ?? c.textDelta) as string | undefined;\n if (text) {\n if (debug) {\n console.debug(`[juneau] streamToWire → yielding text ${JSON.stringify(text)}`);\n }\n yield `data: ${JSON.stringify({ type: 'text', text })}\\n\\n`;\n }\n }\n\n // All other chunk types are intentionally silent — the SkillSet execute fn\n // emits activity events into its buffer which we drain above.\n }\n\n // Final drain — catches activity events from the last tool call when no\n // text-delta followed (Gemini sometimes calls a tool successfully but emits\n // the reply text in a subsequent step that never arrives as text-delta here).\n if (skillSet) {\n const remaining = skillSet.drainActivities();\n if (debug && remaining.length > 0) {\n console.debug(`[juneau] streamToWire → draining ${remaining.length} activities (end of stream)`);\n }\n for (const event of remaining) {\n yield event;\n }\n }\n\n if (debug) {\n console.debug('[juneau] streamToWire → end of stream, emitting done');\n }\n\n yield `data: ${JSON.stringify({ type: 'done' })}\\n\\n`;\n}\n","import type { ToolRecoveryOptions } from './types';\nimport { streamToWire } from './streamToWire';\n\n/**\n * Encapsulates the two-phase streaming pattern needed for Gemini 2.5 Flash\n * tool call scenarios where the model either fails or silently succeeds without\n * emitting any text in the same stream pass.\n *\n * Phase 1: Stream with tools, maxSteps: 2 (or higher). Collect whether any\n * text was produced and whether a tool failure occurred.\n *\n * Phase 2 triggers when a tool failed AND no text was produced:\n * - Injects failureContext as an assistant message so the model is forced to\n * write a recovery text response (no tools available in phase 2).\n *\n * Silent-success path (hadFailure=false, textProduced=false):\n * - Gemini called the tool successfully but wrote nothing. This is a known\n * Gemini 2.5 Flash behaviour when maxSteps > 1: the tool result and the\n * model's reply text arrive as separate steps, and text-delta may not be\n * emitted by the time phase 1's fullStream is exhausted. The debug logs\n * will reveal the exact chunk sequence. In this state we still emit done\n * cleanly — the consumer can decide whether to retry.\n *\n * @returns AsyncIterable<string> of Juneau wire SSE strings (activities + text + done)\n */\nexport async function* withToolRecovery(options: ToolRecoveryOptions): AsyncIterable<string> {\n const { phase1, phase2, skillSet, debug = false } = options;\n\n if (debug) {\n console.debug('[juneau] withToolRecovery → phase 1 start');\n }\n\n let producedText = false;\n\n const result1 = phase1();\n\n for await (const chunk of streamToWire(result1.fullStream, skillSet, { debug })) {\n // Track whether any text reached the wire (done event doesn't count)\n if (chunk.includes('\"type\":\"text\"')) {\n producedText = true;\n }\n\n // Hold back the done event — we may need to continue with phase 2\n if (chunk.includes('\"type\":\"done\"')) {\n continue;\n }\n\n yield chunk;\n }\n\n if (debug) {\n console.debug(\n `[juneau] withToolRecovery → phase 1 complete, textProduced=${producedText}, hadFailure=${skillSet.hadFailure}`\n );\n }\n\n if (skillSet.hadFailure && !producedText && skillSet.failureContext) {\n // Tool failed and model wrote nothing — run phase 2 to force a text response\n if (debug) {\n console.debug('[juneau] withToolRecovery → hadFailure=true, textProduced=false → triggering phase 2');\n }\n\n const result2 = phase2(skillSet.failureContext);\n\n // Phase 2 has no skillSet — plain text response, no tool calls expected\n yield* streamToWire(result2.fullStream, undefined, { debug });\n\n if (debug) {\n console.debug('[juneau] withToolRecovery → phase 2 complete');\n }\n } else if (!producedText && !skillSet.hadFailure) {\n // Tool succeeded but model emitted no text — Gemini silent-response scenario.\n // Log clearly so the consumer can see this in debug output and diagnose\n // whether text arrived in a different chunk type or was simply not generated.\n if (debug) {\n console.debug(\n '[juneau] withToolRecovery → no failure, no text — emitting done (possible Gemini silent response)'\n );\n }\n\n yield `data: ${JSON.stringify({ type: 'done' })}\\n\\n`;\n } else {\n // Normal success — text was produced, emit done\n yield `data: ${JSON.stringify({ type: 'done' })}\\n\\n`;\n }\n}\n"],"names":["toSdkMessages","messages","mapped","msg","text","p","result","i","current","prev","formatActivityEvent","event","pickLabel","labels","language","createSkillSet","skills","options","debug","activityBuffer","hadFailure","failureContext","tools","name","skill","input","startMs","durationMs","err","message","failedLabels","TEXT_CHUNK_TYPES","IGNORED_CHUNK_TYPES","streamToWire","fullStream","skillSet","chunk","c","type","activities","remaining","withToolRecovery","phase1","phase2","producedText","result1","result2"],"mappings":"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"}
@@ -1,12 +1,15 @@
1
- import type { SkillSet } from './types';
1
+ import type { SkillSet, StreamToWireOptions } from './types';
2
2
  /**
3
3
  * Converts ai-sdk's fullStream AsyncIterable into Juneau wire SSE strings.
4
4
  *
5
5
  * Handles:
6
- * - ai-sdk v7 chunk property rename (chunk.text) with fallback to v6 (chunk.textDelta)
7
- * - Flushing skillSet.drainActivities() before each text chunk
8
- * - A final drain after the loop ends (catches last tool call activities)
6
+ * - All text chunk types used by ai-sdk v7 (text-delta, text) incl. v6 textDelta fallback
7
+ * - Flushing skillSet.drainActivities() before each text chunk so activities
8
+ * always appear before the text they precede
9
+ * - A final drain after the loop ends (catches last-tool-call activities when
10
+ * no text follows the tool result — the Gemini silent-response scenario)
9
11
  * - Emitting the done event when the stream finishes
12
+ * - Structured debug logging of every chunk when options.debug is true
10
13
  */
11
- export declare function streamToWire(fullStream: AsyncIterable<unknown>, skillSet?: SkillSet): AsyncIterable<string>;
14
+ export declare function streamToWire(fullStream: AsyncIterable<unknown>, skillSet?: SkillSet, options?: StreamToWireOptions): AsyncIterable<string>;
12
15
  //# sourceMappingURL=streamToWire.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"streamToWire.d.ts","sourceRoot":"","sources":["../../src/server/streamToWire.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC;;;;;;;;GAQG;AACH,wBAAuB,YAAY,CACjC,UAAU,EAAE,aAAa,CAAC,OAAO,CAAC,EAClC,QAAQ,CAAC,EAAE,QAAQ,GAClB,aAAa,CAAC,MAAM,CAAC,CAgCvB"}
1
+ {"version":3,"file":"streamToWire.d.ts","sourceRoot":"","sources":["../../src/server/streamToWire.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAyB7D;;;;;;;;;;;GAWG;AACH,wBAAuB,YAAY,CACjC,UAAU,EAAE,aAAa,CAAC,OAAO,CAAC,EAClC,QAAQ,CAAC,EAAE,QAAQ,EACnB,OAAO,CAAC,EAAE,mBAAmB,GAC5B,aAAa,CAAC,MAAM,CAAC,CAqEvB"}
@@ -46,6 +46,8 @@ export interface SkillSet {
46
46
  export interface SkillSetOptions {
47
47
  /** Language code — determines which label variant to use. Default: 'en' */
48
48
  language?: string;
49
+ /** Emit structured console.debug logs for each skill execution. Default: false */
50
+ debug?: boolean;
49
51
  }
50
52
  export interface ToolRecoveryOptions {
51
53
  phase1: () => {
@@ -55,6 +57,12 @@ export interface ToolRecoveryOptions {
55
57
  fullStream: AsyncIterable<unknown>;
56
58
  };
57
59
  skillSet: SkillSet;
60
+ /** Emit structured console.debug logs for phase decisions and chunk flow. Default: false */
61
+ debug?: boolean;
62
+ }
63
+ export interface StreamToWireOptions {
64
+ /** Emit structured console.debug logs for every ai-sdk chunk received. Default: false */
65
+ debug?: boolean;
58
66
  }
59
67
  /**
60
68
  * CoreMessage — structural match for ai-sdk's CoreMessage type.
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAEtC;;GAEG;AACH,MAAM,WAAW,eAAe,CAAC,MAAM,SAAS,OAAO,GAAG,OAAO;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE;QACN,OAAO,EAAE;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAA;SAAE,CAAC;QACpC,IAAI,EAAK;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAA;SAAE,CAAC;QACpC,MAAM,CAAC,EAAE;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAA;SAAE,CAAC;KACrC,CAAC;IACF,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACvD;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE/B;;;OAGG;IACH,eAAe,IAAI,MAAM,EAAE,CAAC;IAE5B;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAC;IAEpB;;;OAGG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM;QAAE,UAAU,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC;IACrD,MAAM,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK;QAAE,UAAU,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC;IAC3E,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/server/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAEtC;;GAEG;AACH,MAAM,WAAW,eAAe,CAAC,MAAM,SAAS,OAAO,GAAG,OAAO;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE;QACN,OAAO,EAAE;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAA;SAAE,CAAC;QACpC,IAAI,EAAK;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAA;SAAE,CAAC;QACpC,MAAM,CAAC,EAAE;YAAE,EAAE,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAA;SAAE,CAAC;KACrC,CAAC;IACF,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACvD;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE/B;;;OAGG;IACH,eAAe,IAAI,MAAM,EAAE,CAAC;IAE5B;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAC;IAEpB;;;OAGG;IACH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kFAAkF;IAClF,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM;QAAE,UAAU,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC;IACrD,MAAM,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK;QAAE,UAAU,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC;IAC3E,QAAQ,EAAE,QAAQ,CAAC;IACnB,4FAA4F;IAC5F,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,yFAAyF;IACzF,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC"}
@@ -1,12 +1,23 @@
1
1
  import type { ToolRecoveryOptions } from './types';
2
2
  /**
3
- * Encapsulates the two-phase streaming pattern needed when a tool fails and
4
- * the model retries the tool instead of writing text (observed with Gemini 2.5 Flash).
3
+ * Encapsulates the two-phase streaming pattern needed for Gemini 2.5 Flash
4
+ * tool call scenarios where the model either fails or silently succeeds without
5
+ * emitting any text in the same stream pass.
5
6
  *
6
- * Phase 1: Stream with tools, maxSteps: 1. Collect whether any text was produced.
7
- * Phase 2: If a tool failed and no text was produced, call the model again without
8
- * tools, injecting the failure context as an assistant message so the model
9
- * is forced to write a text response.
7
+ * Phase 1: Stream with tools, maxSteps: 2 (or higher). Collect whether any
8
+ * text was produced and whether a tool failure occurred.
9
+ *
10
+ * Phase 2 triggers when a tool failed AND no text was produced:
11
+ * - Injects failureContext as an assistant message so the model is forced to
12
+ * write a recovery text response (no tools available in phase 2).
13
+ *
14
+ * Silent-success path (hadFailure=false, textProduced=false):
15
+ * - Gemini called the tool successfully but wrote nothing. This is a known
16
+ * Gemini 2.5 Flash behaviour when maxSteps > 1: the tool result and the
17
+ * model's reply text arrive as separate steps, and text-delta may not be
18
+ * emitted by the time phase 1's fullStream is exhausted. The debug logs
19
+ * will reveal the exact chunk sequence. In this state we still emit done
20
+ * cleanly — the consumer can decide whether to retry.
10
21
  *
11
22
  * @returns AsyncIterable<string> of Juneau wire SSE strings (activities + text + done)
12
23
  */
@@ -1 +1 @@
1
- {"version":3,"file":"withToolRecovery.d.ts","sourceRoot":"","sources":["../../src/server/withToolRecovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAGnD;;;;;;;;;;GAUG;AACH,wBAAuB,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,GAAG,aAAa,CAAC,MAAM,CAAC,CAgC3F"}
1
+ {"version":3,"file":"withToolRecovery.d.ts","sourceRoot":"","sources":["../../src/server/withToolRecovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAGnD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAuB,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,GAAG,aAAa,CAAC,MAAM,CAAC,CA4D3F"}
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- :root{--juneau-color-primary: #000000;--juneau-color-primary-dark: #252528;--juneau-color-primary-light: #ECECEC;--juneau-color-primary-border: #E8E8EC;--juneau-color-accent: #FF49A4;--juneau-color-accent-dark: #FF6BB3;--juneau-color-accent-light: #FFF0F7;--juneau-color-accent-border: #FFBDD9;--juneau-color-surface: #FFFFFF;--juneau-color-surface-raised: #F4F4F6;--juneau-color-surface-hover: #ECECEC;--juneau-color-border: #E8E8EC;--juneau-color-border-subtle: #ECECEC;--juneau-color-text-primary: #000000;--juneau-color-text-secondary: #474747;--juneau-color-text-muted: #474747;--juneau-color-text-faint: #9090A0;--juneau-color-text-inverse: #FFFFFF;--juneau-color-assistant-avatar: #FF49A4;--juneau-color-error-bg: #FFF0F7;--juneau-color-error-border: #E6244A;--juneau-color-error-text: #C41A3B;--juneau-color-success-bg: #F4FDE8;--juneau-color-success-text: #3F7528;--juneau-color-warning-bg: #FFF6E5;--juneau-color-warning-text: #B37400;--juneau-color-danger-bg: #FFF0F7;--juneau-color-danger-text: #C41A3B;--juneau-radius-sm: 6px;--juneau-radius-md: 8px;--juneau-radius-lg: 12px;--juneau-radius-full: 9999px;--juneau-font-size-xs: 11px;--juneau-font-size-sm: 12px;--juneau-font-size-base: 13px;--juneau-font-size-md: 14px;--juneau-font-size-lg: 15px;--juneau-font-size-xl: 16px;--juneau-font-size-2xl: 22px;--juneau-font-size-3xl: 24px;--juneau-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--juneau-transition-fast: .15s}._root_14q8g_1{display:contents;font-family:var(--juneau-font-family)}._wrapper_v5oy7_1{overflow-x:auto;margin:8px 0;border-radius:var(--juneau-radius-md);border:1px solid var(--juneau-color-border)}._table_v5oy7_8{width:100%;border-collapse:collapse;font-size:var(--juneau-font-size-base)}._th_v5oy7_14{background-color:var(--juneau-color-surface-raised);padding:8px 12px;text-align:left;font-weight:600;color:var(--juneau-color-text-secondary);border-bottom:1px solid var(--juneau-color-border);white-space:nowrap}._tr_v5oy7_24:nth-child(2n){background-color:var(--juneau-color-surface-raised)}._tr_v5oy7_24:hover{background-color:var(--juneau-color-surface-hover)}._td_v5oy7_32{padding:7px 12px;color:var(--juneau-color-text-primary);border-bottom:1px solid var(--juneau-color-border-subtle)}._card_q8nsq_1{background:linear-gradient(135deg,var(--juneau-color-accent-light) 0%,var(--juneau-color-accent-border) 100%);border:1px solid var(--juneau-color-accent-border);border-radius:var(--juneau-radius-lg);padding:14px 16px;margin:8px 0}._header_q8nsq_9{display:flex;align-items:center;gap:8px;margin-bottom:6px}._icon_q8nsq_16{flex-shrink:0;width:16px;height:16px;color:var(--juneau-color-accent)}._title_q8nsq_23{font-weight:600;font-size:var(--juneau-font-size-md);color:var(--juneau-color-accent-dark)}._description_q8nsq_29{font-size:var(--juneau-font-size-base);color:var(--juneau-color-text-secondary);margin:0 0 12px;line-height:1.5}._actions_q8nsq_36{display:flex;gap:8px}._confirm_q8nsq_41{padding:7px 16px;background-color:var(--juneau-color-accent);color:var(--juneau-color-text-inverse);border:none;border-radius:var(--juneau-radius-sm);font-size:var(--juneau-font-size-base);font-weight:500;cursor:pointer;transition:background-color var(--juneau-transition-fast)}._confirm_q8nsq_41:hover{background-color:var(--juneau-color-accent-dark)}._cancel_q8nsq_57{padding:7px 16px;background-color:transparent;color:var(--juneau-color-text-muted);border:1px solid var(--juneau-color-border);border-radius:var(--juneau-radius-sm);font-size:var(--juneau-font-size-base);font-weight:500;cursor:pointer;transition:background-color var(--juneau-transition-fast),color var(--juneau-transition-fast)}._cancel_q8nsq_57:hover{background-color:var(--juneau-color-surface-hover);color:var(--juneau-color-text-secondary)}._activity_2jjfs_1{display:flex;align-items:flex-start;gap:8px;padding:7px 10px;margin:4px 0;border:1px solid var(--juneau-color-border);border-radius:var(--juneau-radius-md);background:var(--juneau-color-surface-raised);font-size:var(--juneau-font-size-sm)}._indicator_2jjfs_13{width:7px;height:7px;margin-top:5px;border-radius:var(--juneau-radius-full);background:var(--juneau-color-text-faint);flex-shrink:0;transition:background var(--juneau-transition-fast)}._activity_2jjfs_1[data-status=running] ._indicator_2jjfs_13{background:var(--juneau-color-accent);animation:_pulse_2jjfs_1 1.4s ease-in-out infinite}._activity_2jjfs_1[data-status=done] ._indicator_2jjfs_13{background:var(--juneau-color-success-text)}._activity_2jjfs_1[data-status=failed] ._indicator_2jjfs_13{background:var(--juneau-color-error-text)}._content_2jjfs_36{min-width:0}._title_2jjfs_40{font-weight:500;color:var(--juneau-color-text-secondary)}._description_2jjfs_45{margin-top:1px;color:var(--juneau-color-text-faint)}@keyframes _pulse_2jjfs_1{0%,to{opacity:1}50%{opacity:.35}}._markdown_187bn_1{line-height:1.6;font-size:var(--juneau-font-size-md)}._markdown_187bn_1 p{margin:0 0 .5em}._markdown_187bn_1 p:last-child{margin-bottom:0}._markdown_187bn_1 strong{font-weight:700}._markdown_187bn_1 em{font-style:italic}._markdown_187bn_1 code{font-family:ui-monospace,Cascadia Code,Source Code Pro,Menlo,monospace;font-size:.9em;background:var(--juneau-color-surface-raised);border:1px solid var(--juneau-color-border);border-radius:var(--juneau-radius-sm);padding:.1em .35em}._markdown_187bn_1 pre{background:var(--juneau-color-surface-raised);border:1px solid var(--juneau-color-border);border-radius:var(--juneau-radius-md);padding:10px 14px;overflow-x:auto;margin:.5em 0}._markdown_187bn_1 pre code{background:none;border:none;padding:0;font-size:var(--juneau-font-size-sm)}._markdown_187bn_1 ul,._markdown_187bn_1 ol{margin:.25em 0 .5em;padding-left:1.4em}._markdown_187bn_1 li{margin-bottom:.2em}._markdown_187bn_1 blockquote{margin:.5em 0;padding-left:.75em;border-left:3px solid var(--juneau-color-border);color:var(--juneau-color-text-secondary)}._markdown_187bn_1 a{color:var(--juneau-color-accent);text-decoration:underline}._markdown_187bn_1 h1,._markdown_187bn_1 h2,._markdown_187bn_1 h3,._markdown_187bn_1 h4{margin:.6em 0 .3em;font-weight:700;line-height:1.3}._markdown_187bn_1 h1{font-size:var(--juneau-font-size-xl)}._markdown_187bn_1 h2{font-size:var(--juneau-font-size-lg)}._markdown_187bn_1 h3,._markdown_187bn_1 h4{font-size:var(--juneau-font-size-md)}._errorText_187bn_83{display:inline-flex;align-items:center;gap:5px;color:var(--juneau-color-error-text);font-size:var(--juneau-font-size-base)}._errorIcon_187bn_91{width:14px;height:14px;flex-shrink:0}._wrapper_13l1e_1{display:flex;align-items:flex-start;gap:8px;padding:4px 0}._user_13l1e_8{flex-direction:row-reverse}._assistant_13l1e_12{flex-direction:row}._avatar_13l1e_16{flex-shrink:0;width:30px;height:30px;border-radius:var(--juneau-radius-full);font-size:var(--juneau-font-size-xs);font-weight:700;display:flex;align-items:center;justify-content:center;margin-top:2px}._user_13l1e_8 ._avatar_13l1e_16{background-color:var(--juneau-color-primary);color:var(--juneau-color-text-inverse)}._assistant_13l1e_12 ._avatar_13l1e_16{background-color:var(--juneau-color-assistant-avatar);color:var(--juneau-color-text-inverse)}._bubble_13l1e_39{max-width:85%;padding:10px 14px;border-radius:var(--juneau-radius-lg);font-size:var(--juneau-font-size-md);line-height:1.6;word-break:break-word}._user_13l1e_8 ._bubble_13l1e_39{background-color:var(--juneau-color-primary);color:var(--juneau-color-text-inverse);border-bottom-right-radius:3px}._assistant_13l1e_12 ._bubble_13l1e_39{background-color:var(--juneau-color-surface-raised);color:var(--juneau-color-text-primary);border:1px solid var(--juneau-color-border);border-bottom-left-radius:3px}._empty_13l1e_61{display:inline-block;width:4px;height:16px}._wrapper_1dvvu_1{display:flex;align-items:center;gap:4px;padding:10px 14px}._dot_1dvvu_8{width:7px;height:7px;border-radius:var(--juneau-radius-full);background-color:var(--juneau-color-text-faint);animation:_bounce_1dvvu_1 1.2s infinite ease-in-out}._dot_1dvvu_8:nth-child(2){animation-delay:.2s}._dot_1dvvu_8:nth-child(3){animation-delay:.4s}._connectingLabel_1dvvu_19{margin-left:4px;font-size:var(--juneau-font-size-sm);color:var(--juneau-color-text-faint)}@keyframes _bounce_1dvvu_1{0%,80%,to{transform:translateY(0);opacity:.5}40%{transform:translateY(-5px);opacity:1}}._list_1f8gn_1{flex:1;overflow-y:auto;padding:16px 12px;display:flex;flex-direction:column;gap:12px;scroll-behavior:smooth}._empty_1f8gn_11{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;color:var(--juneau-color-text-faint);font-size:var(--juneau-font-size-md);gap:6px;padding:40px 20px}._hint_1f8gn_24{font-size:var(--juneau-font-size-sm);color:var(--juneau-color-text-faint)}._typing_1f8gn_31{display:flex;align-items:flex-start;gap:8px;padding:2px 0}._wrapper_v8iyr_1{display:flex;flex-direction:column;border-top:1px solid var(--juneau-color-border);background-color:var(--juneau-color-surface);flex-shrink:0}._textarea_v8iyr_11{resize:none;border:none;outline:none;padding:12px 14px 6px;font-size:var(--juneau-font-size-md);font-family:inherit;line-height:1.6;color:var(--juneau-color-text-primary);background-color:var(--juneau-color-surface);width:100%}._textarea_v8iyr_11::placeholder{color:var(--juneau-color-text-faint)}._textarea_v8iyr_11:disabled{opacity:.6;cursor:not-allowed}._toolbar_v8iyr_35{display:flex;align-items:center;justify-content:space-between;padding:6px 10px 10px;gap:8px}._actions_v8iyr_45{display:flex;align-items:center;gap:2px;flex-wrap:wrap}._actionBtn_v8iyr_52{display:flex;align-items:center;gap:5px;padding:5px 8px;border:none;border-radius:var(--juneau-radius-sm);background:transparent;color:var(--juneau-color-text-muted);font-size:var(--juneau-font-size-sm);font-family:inherit;cursor:pointer;white-space:nowrap;transition:background-color var(--juneau-transition-fast),color var(--juneau-transition-fast)}._actionBtn_v8iyr_52 svg{font-size:11px;opacity:.8}._actionBtn_v8iyr_52:hover:not(:disabled){background:var(--juneau-color-surface-raised);color:var(--juneau-color-text-primary)}._actionBtn_v8iyr_52:disabled{opacity:.4;cursor:not-allowed}._sendBtn_v8iyr_85{display:flex;align-items:center;gap:7px;padding:7px 14px;border:none;border-radius:var(--juneau-radius-md);background-color:var(--juneau-color-primary);color:var(--juneau-color-text-inverse);font-size:var(--juneau-font-size-base);font-family:inherit;font-weight:600;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:background-color var(--juneau-transition-fast),opacity var(--juneau-transition-fast)}._sendBtn_v8iyr_85 svg{font-size:12px}._sendBtn_v8iyr_85:hover:not(:disabled){background-color:var(--juneau-color-primary-dark)}._sendBtn_v8iyr_85:disabled{opacity:.4;cursor:not-allowed}._spinnerIcon_v8iyr_116{width:14px;height:14px;animation:_spin_v8iyr_116 .8s linear infinite}@keyframes _spin_v8iyr_116{to{transform:rotate(360deg)}}._stopBtn_v8iyr_126{display:flex;align-items:center;gap:7px;padding:7px 14px;border:1px solid var(--juneau-color-border);border-radius:var(--juneau-radius-md);background-color:var(--juneau-color-surface);color:var(--juneau-color-text-secondary);font-size:var(--juneau-font-size-base);font-family:inherit;font-weight:600;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:background-color var(--juneau-transition-fast),color var(--juneau-transition-fast)}._stopBtn_v8iyr_126:hover{background-color:var(--juneau-color-surface-raised);color:var(--juneau-color-text-primary)}._stopIcon_v8iyr_149{width:14px;height:14px}._wrapper_1kefv_1{display:flex;align-items:center;gap:8px;padding:10px 14px;background-color:var(--juneau-color-error-bg);border:1px solid var(--juneau-color-error-border);border-radius:var(--juneau-radius-md);margin:4px 12px;font-size:var(--juneau-font-size-base);color:var(--juneau-color-error-text)}._icon_1kefv_14{flex-shrink:0;width:16px;height:16px}._message_1kefv_20{flex:1}._dismiss_1kefv_24{display:flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;color:var(--juneau-color-error-text);padding:0 2px;opacity:.7;transition:opacity var(--juneau-transition-fast)}._dismiss_1kefv_24 svg{width:14px;height:14px}._dismiss_1kefv_24:hover{opacity:1}._chat_1acbe_1{display:flex;flex-direction:column;height:100%;overflow:hidden}._header_f9hy1_1{display:flex;align-items:center;justify-content:space-between;padding:8px 14px;border-bottom:1px solid var(--juneau-color-border);background:var(--juneau-color-primary);color:var(--juneau-color-text-inverse);flex-shrink:0}._left_f9hy1_12{display:flex;align-items:center;gap:8px}._icon_f9hy1_18{font-size:14px;opacity:.9}._title_f9hy1_23{font-size:var(--juneau-font-size-base);font-weight:600;letter-spacing:.02em}._resetBtn_f9hy1_29{background:transparent;border:none;border-radius:var(--juneau-radius-sm);color:var(--juneau-color-text-inverse);width:26px;height:26px;font-size:14px;cursor:pointer;display:flex;align-items:center;justify-content:center;opacity:.7;transition:background-color var(--juneau-transition-fast),opacity var(--juneau-transition-fast)}._resetBtn_f9hy1_29:hover{background:#ffffff26;opacity:1}._actions_f9hy1_50{display:flex;align-items:center;gap:2px}._sidebar_17ski_1{position:fixed;bottom:0;right:0;display:flex;flex-direction:column;width:420px;background-color:var(--juneau-color-surface);border-left:1px solid var(--juneau-color-border);border-top:1px solid var(--juneau-color-border);border-radius:var(--juneau-radius-lg) var(--juneau-radius-lg) 0 0;box-shadow:-4px 0 24px #00000014;overflow:hidden;z-index:200;transition:height .25s cubic-bezier(.4,0,.2,1),width .25s cubic-bezier(.4,0,.2,1),box-shadow .25s ease}._minimized_17ski_20{height:auto!important;width:280px;box-shadow:0 -2px 16px #0000001a}
1
+ :root{--juneau-color-primary: #000000;--juneau-color-primary-dark: #252528;--juneau-color-primary-light: #ECECEC;--juneau-color-primary-border: #E8E8EC;--juneau-color-accent: #FF49A4;--juneau-color-accent-dark: #FF6BB3;--juneau-color-accent-light: #FFF0F7;--juneau-color-accent-border: #FFBDD9;--juneau-color-surface: #FFFFFF;--juneau-color-surface-raised: #F4F4F6;--juneau-color-surface-hover: #ECECEC;--juneau-color-border: #E8E8EC;--juneau-color-border-subtle: #ECECEC;--juneau-color-text-primary: #000000;--juneau-color-text-secondary: #474747;--juneau-color-text-muted: #474747;--juneau-color-text-faint: #9090A0;--juneau-color-text-inverse: #FFFFFF;--juneau-color-assistant-avatar: #FF49A4;--juneau-color-error-bg: #FFF0F7;--juneau-color-error-border: #E6244A;--juneau-color-error-text: #C41A3B;--juneau-color-success-bg: #F4FDE8;--juneau-color-success-text: #3F7528;--juneau-color-warning-bg: #FFF6E5;--juneau-color-warning-text: #B37400;--juneau-color-danger-bg: #FFF0F7;--juneau-color-danger-text: #C41A3B;--juneau-radius-sm: 6px;--juneau-radius-md: 8px;--juneau-radius-lg: 12px;--juneau-radius-full: 9999px;--juneau-font-size-xs: 11px;--juneau-font-size-sm: 12px;--juneau-font-size-base: 13px;--juneau-font-size-md: 14px;--juneau-font-size-lg: 15px;--juneau-font-size-xl: 16px;--juneau-font-size-2xl: 22px;--juneau-font-size-3xl: 24px;--juneau-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--juneau-transition-fast: .15s}._root_1etc2_1{display:contents;font-family:var(--juneau-font-family)}._wrapper_izvjy_1{overflow-x:auto;margin:8px 0;border-radius:var(--juneau-radius-md);border:1px solid var(--juneau-color-border)}._table_izvjy_15{width:100%;border-collapse:collapse;font-size:var(--juneau-font-size-base)}._th_izvjy_27{background-color:var(--juneau-color-surface-raised);padding:8px 12px;text-align:left;font-weight:600;color:var(--juneau-color-text-secondary);border-bottom:1px solid var(--juneau-color-border);white-space:nowrap}._tr_izvjy_47:nth-child(2n){background-color:var(--juneau-color-surface-raised)}._tr_izvjy_47:hover{background-color:var(--juneau-color-surface-hover)}._td_izvjy_63{padding:7px 12px;color:var(--juneau-color-text-primary);border-bottom:1px solid var(--juneau-color-border-subtle)}._card_qrvry_1{background:linear-gradient(135deg,var(--juneau-color-accent-light) 0%,var(--juneau-color-accent-border) 100%);border:1px solid var(--juneau-color-accent-border);border-radius:var(--juneau-radius-lg);padding:14px 16px;margin:8px 0}._header_qrvry_17{display:flex;align-items:center;gap:8px;margin-bottom:6px}._icon_qrvry_31{flex-shrink:0;width:16px;height:16px;color:var(--juneau-color-accent)}._title_qrvry_45{font-weight:600;font-size:var(--juneau-font-size-md);color:var(--juneau-color-accent-dark)}._description_qrvry_57{font-size:var(--juneau-font-size-base);color:var(--juneau-color-text-secondary);margin:0 0 12px;line-height:1.5}._actions_qrvry_71{display:flex;gap:8px}._confirm_qrvry_81{padding:7px 16px;background-color:var(--juneau-color-accent);color:var(--juneau-color-text-inverse);border:none;border-radius:var(--juneau-radius-sm);font-size:var(--juneau-font-size-base);font-weight:500;cursor:pointer;transition:background-color var(--juneau-transition-fast)}._confirm_qrvry_81:hover{background-color:var(--juneau-color-accent-dark)}._cancel_qrvry_113{padding:7px 16px;background-color:transparent;color:var(--juneau-color-text-muted);border:1px solid var(--juneau-color-border);border-radius:var(--juneau-radius-sm);font-size:var(--juneau-font-size-base);font-weight:500;cursor:pointer;transition:background-color var(--juneau-transition-fast),color var(--juneau-transition-fast)}._cancel_qrvry_113:hover{background-color:var(--juneau-color-surface-hover);color:var(--juneau-color-text-secondary)}._activity_1661l_1{display:flex;align-items:flex-start;gap:8px;padding:7px 10px;margin:4px 0;border:1px solid var(--juneau-color-border);border-radius:var(--juneau-radius-md);background:var(--juneau-color-surface-raised);font-size:var(--juneau-font-size-sm)}._indicator_1661l_25{width:7px;height:7px;margin-top:5px;border-radius:var(--juneau-radius-full);background:var(--juneau-color-text-faint);flex-shrink:0;transition:background var(--juneau-transition-fast)}._activity_1661l_1[data-status=running] ._indicator_1661l_25{background:var(--juneau-color-accent);animation:_pulse_1661l_1 1.4s ease-in-out infinite}._activity_1661l_1[data-status=done] ._indicator_1661l_25{background:var(--juneau-color-success-text)}._activity_1661l_1[data-status=failed] ._indicator_1661l_25{background:var(--juneau-color-error-text)}._content_1661l_71{min-width:0}._title_1661l_79{font-weight:500;color:var(--juneau-color-text-secondary)}._description_1661l_89{margin-top:1px;color:var(--juneau-color-text-faint)}@keyframes _pulse_1661l_1{0%,to{opacity:1}50%{opacity:.35}}._markdown_97mdk_1{line-height:1.6;font-size:var(--juneau-font-size-md)}._markdown_97mdk_1 p{margin:0 0 .5em}._markdown_97mdk_1 p:last-child{margin-bottom:0}._markdown_97mdk_1 strong{font-weight:700}._markdown_97mdk_1 em{font-style:italic}._markdown_97mdk_1 code{font-family:ui-monospace,Cascadia Code,Source Code Pro,Menlo,monospace;font-size:.9em;background:var(--juneau-color-surface-raised);border:1px solid var(--juneau-color-border);border-radius:var(--juneau-radius-sm);padding:.1em .35em}._markdown_97mdk_1 pre{background:var(--juneau-color-surface-raised);border:1px solid var(--juneau-color-border);border-radius:var(--juneau-radius-md);padding:10px 14px;overflow-x:auto;margin:.5em 0}._markdown_97mdk_1 pre code{background:none;border:none;padding:0;font-size:var(--juneau-font-size-sm)}._markdown_97mdk_1 ul,._markdown_97mdk_1 ol{margin:.25em 0 .5em;padding-left:1.4em}._markdown_97mdk_1 li{margin-bottom:.2em}._markdown_97mdk_1 blockquote{margin:.5em 0;padding-left:.75em;border-left:3px solid var(--juneau-color-border);color:var(--juneau-color-text-secondary)}._markdown_97mdk_1 a{color:var(--juneau-color-accent);text-decoration:underline}._markdown_97mdk_1 h1,._markdown_97mdk_1 h2,._markdown_97mdk_1 h3,._markdown_97mdk_1 h4{margin:.6em 0 .3em;font-weight:700;line-height:1.3}._markdown_97mdk_1 h1{font-size:var(--juneau-font-size-xl)}._markdown_97mdk_1 h2{font-size:var(--juneau-font-size-lg)}._markdown_97mdk_1 h3,._markdown_97mdk_1 h4{font-size:var(--juneau-font-size-md)}._errorText_97mdk_165{display:inline-flex;align-items:center;gap:5px;color:var(--juneau-color-error-text);font-size:var(--juneau-font-size-base)}._errorIcon_97mdk_181{width:14px;height:14px;flex-shrink:0}._wrapper_1jkxo_1{display:flex;align-items:flex-start;gap:8px;padding:4px 0}._user_1jkxo_15{flex-direction:row-reverse}._assistant_1jkxo_23{flex-direction:row}._avatar_1jkxo_31{flex-shrink:0;width:30px;height:30px;border-radius:var(--juneau-radius-full);font-size:var(--juneau-font-size-xs);font-weight:700;display:flex;align-items:center;justify-content:center;margin-top:2px}._user_1jkxo_15 ._avatar_1jkxo_31{background-color:var(--juneau-color-primary);color:var(--juneau-color-text-inverse)}._assistant_1jkxo_23 ._avatar_1jkxo_31{background-color:var(--juneau-color-assistant-avatar);color:var(--juneau-color-text-inverse)}._bubble_1jkxo_77{max-width:85%;padding:10px 14px;border-radius:var(--juneau-radius-lg);font-size:var(--juneau-font-size-md);line-height:1.6;word-break:break-word}._user_1jkxo_15 ._bubble_1jkxo_77{background-color:var(--juneau-color-primary);color:var(--juneau-color-text-inverse);border-bottom-right-radius:3px}._assistant_1jkxo_23 ._bubble_1jkxo_77{background-color:var(--juneau-color-surface-raised);color:var(--juneau-color-text-primary);border:1px solid var(--juneau-color-border);border-bottom-left-radius:3px}._empty_1jkxo_121{display:inline-block;width:4px;height:16px}._wrapper_1oco7_1{display:flex;align-items:center;gap:4px;padding:10px 14px}._dot_1oco7_15{width:7px;height:7px;border-radius:var(--juneau-radius-full);background-color:var(--juneau-color-text-faint);animation:_bounce_1oco7_1 1.2s infinite ease-in-out}._dot_1oco7_15:nth-child(2){animation-delay:.2s}._dot_1oco7_15:nth-child(3){animation-delay:.4s}._connectingLabel_1oco7_37{margin-left:4px;font-size:var(--juneau-font-size-sm);color:var(--juneau-color-text-faint)}@keyframes _bounce_1oco7_1{0%,80%,to{transform:translateY(0);opacity:.5}40%{transform:translateY(-5px);opacity:1}}._list_ixto8_1{flex:1;overflow-y:auto;padding:16px 12px;display:flex;flex-direction:column;gap:12px;scroll-behavior:smooth}._empty_ixto8_21{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;color:var(--juneau-color-text-faint);font-size:var(--juneau-font-size-md);gap:6px;padding:40px 20px}._hint_ixto8_47{font-size:var(--juneau-font-size-sm);color:var(--juneau-color-text-faint)}._typing_ixto8_61{display:flex;align-items:flex-start;gap:8px;padding:2px 0}._wrapper_ehrkb_1{display:flex;flex-direction:column;border-top:1px solid var(--juneau-color-border);background-color:var(--juneau-color-surface);flex-shrink:0}._textarea_ehrkb_21{resize:none;border:none;outline:none;padding:12px 14px 6px;font-size:var(--juneau-font-size-md);font-family:inherit;line-height:1.6;color:var(--juneau-color-text-primary);background-color:var(--juneau-color-surface);width:100%}._textarea_ehrkb_21::placeholder{color:var(--juneau-color-text-faint)}._textarea_ehrkb_21:disabled{opacity:.6;cursor:not-allowed}._toolbar_ehrkb_69{display:flex;align-items:center;justify-content:space-between;padding:6px 10px 10px;gap:8px}._actions_ehrkb_89{display:flex;align-items:center;gap:2px;flex-wrap:wrap}._actionBtn_ehrkb_103{display:flex;align-items:center;gap:5px;padding:5px 8px;border:none;border-radius:var(--juneau-radius-sm);background:transparent;color:var(--juneau-color-text-muted);font-size:var(--juneau-font-size-sm);font-family:inherit;cursor:pointer;white-space:nowrap;transition:background-color var(--juneau-transition-fast),color var(--juneau-transition-fast)}._actionBtn_ehrkb_103 svg{font-size:11px;opacity:.8}._actionBtn_ehrkb_103:hover:not(:disabled){background:var(--juneau-color-surface-raised);color:var(--juneau-color-text-primary)}._actionBtn_ehrkb_103:disabled{opacity:.4;cursor:not-allowed}._sendBtn_ehrkb_169{display:flex;align-items:center;gap:7px;padding:7px 14px;border:none;border-radius:var(--juneau-radius-md);background-color:var(--juneau-color-primary);color:var(--juneau-color-text-inverse);font-size:var(--juneau-font-size-base);font-family:inherit;font-weight:600;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:background-color var(--juneau-transition-fast),opacity var(--juneau-transition-fast)}._sendBtn_ehrkb_169 svg{font-size:12px}._sendBtn_ehrkb_169:hover:not(:disabled){background-color:var(--juneau-color-primary-dark)}._sendBtn_ehrkb_169:disabled{opacity:.4;cursor:not-allowed}._spinnerIcon_ehrkb_231{width:14px;height:14px;animation:_spin_ehrkb_231 .8s linear infinite}@keyframes _spin_ehrkb_231{to{transform:rotate(360deg)}}._stopBtn_ehrkb_251{display:flex;align-items:center;gap:7px;padding:7px 14px;border:1px solid var(--juneau-color-border);border-radius:var(--juneau-radius-md);background-color:var(--juneau-color-surface);color:var(--juneau-color-text-secondary);font-size:var(--juneau-font-size-base);font-family:inherit;font-weight:600;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:background-color var(--juneau-transition-fast),color var(--juneau-transition-fast)}._stopBtn_ehrkb_251:hover{background-color:var(--juneau-color-surface-raised);color:var(--juneau-color-text-primary)}._stopIcon_ehrkb_297{width:14px;height:14px}._wrapper_1e3w5_1{display:flex;align-items:center;gap:8px;padding:10px 14px;background-color:var(--juneau-color-error-bg);border:1px solid var(--juneau-color-error-border);border-radius:var(--juneau-radius-md);margin:4px 12px;font-size:var(--juneau-font-size-base);color:var(--juneau-color-error-text)}._icon_1e3w5_27{flex-shrink:0;width:16px;height:16px}._message_1e3w5_39{flex:1}._dismiss_1e3w5_47{display:flex;align-items:center;justify-content:center;background:none;border:none;cursor:pointer;color:var(--juneau-color-error-text);padding:0 2px;opacity:.7;transition:opacity var(--juneau-transition-fast)}._dismiss_1e3w5_47 svg{width:14px;height:14px}._dismiss_1e3w5_47:hover{opacity:1}._chat_13051_1{display:flex;flex-direction:column;height:100%;overflow:hidden}._header_1saak_1{display:flex;align-items:center;justify-content:space-between;padding:8px 14px;border-bottom:1px solid var(--juneau-color-border);background:var(--juneau-color-primary);color:var(--juneau-color-text-inverse);flex-shrink:0}._left_1saak_23{display:flex;align-items:center;gap:8px}._icon_1saak_35{font-size:14px;opacity:.9}._title_1saak_45{font-size:var(--juneau-font-size-base);font-weight:600;letter-spacing:.02em}._resetBtn_1saak_57{background:transparent;border:none;border-radius:var(--juneau-radius-sm);color:var(--juneau-color-text-inverse);width:26px;height:26px;font-size:14px;cursor:pointer;display:flex;align-items:center;justify-content:center;opacity:.7;transition:background-color var(--juneau-transition-fast),opacity var(--juneau-transition-fast)}._resetBtn_1saak_57:hover{background:#ffffff26;opacity:1}._actions_1saak_99{display:flex;align-items:center;gap:2px}._sidebar_u9ybt_1{position:fixed;bottom:0;right:0;display:flex;flex-direction:column;width:420px;background-color:var(--juneau-color-surface);border-left:1px solid var(--juneau-color-border);border-top:1px solid var(--juneau-color-border);border-radius:var(--juneau-radius-lg) var(--juneau-radius-lg) 0 0;box-shadow:-4px 0 24px #00000014;overflow:hidden;z-index:200;transition:height .25s cubic-bezier(.4,0,.2,1),width .25s cubic-bezier(.4,0,.2,1),box-shadow .25s ease}._minimized_u9ybt_39{height:auto!important;width:280px;box-shadow:0 -2px 16px #0000001a}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juneau",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Open-source React/TypeScript library for AI chat UI components",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -14,7 +14,8 @@
14
14
  },
15
15
  "./server": {
16
16
  "types": "./dist/server/index.d.ts",
17
- "import": "./dist/server/index.js"
17
+ "import": "./dist/server/index.js",
18
+ "require": "./dist/server/index.cjs"
18
19
  },
19
20
  "./dist/style.css": "./dist/style.css"
20
21
  },