juneau 0.3.0 → 0.3.1

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,10 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function g(a){const i=a.map(t=>{const n=t.parts.filter(r=>r.type==="text").map(r=>r.text).join("");return{role:t.role,content:n}}),e=[];for(let t=0;t<i.length;t++){const n=i[t],r=e[e.length-1];n.role==="user"&&r?.role==="user"&&e.push({role:"assistant",content:"…"}),e.push(n)}return e.filter(t=>t.content.length>0)}function u(a){return`data: ${JSON.stringify(a)}
2
+
3
+ `}function f(a,i){return a[i]??a.en}function h(a,i={}){const e=i.language??"en",t=[];let n=!1,r=null;const o={};for(const[s,l]of Object.entries(a))o[s]={description:l.description,parameters:l.input,execute:async y=>{t.push(u({type:"activity",id:s,title:f(l.labels.running,e),status:"running",metadata:{skill:s}}));try{const c=await l.execute(y);return t.push(u({type:"activity",id:s,title:f(l.labels.done,e),status:"done",metadata:{skill:s}})),c}catch(c){const p=l.labels.failed??{cs:"Nepodařilo se",en:"Failed"};throw t.push(u({type:"activity",id:s,title:f(p,e),status:"failed",metadata:{skill:s}})),n=!0,r=`Tool "${s}" failed: ${c instanceof Error?c.message:String(c)}`,c}}};return{tools:o,drainActivities(){return t.splice(0,t.length)},get hadFailure(){return n},get failureContext(){return r}}}async function*d(a,i){for await(const e of a){const t=e;if(t.type==="text-delta"){if(i)for(const r of i.drainActivities())yield r;const n=t.text??t.textDelta;n&&(yield`data: ${JSON.stringify({type:"text",text:n})}
4
+
5
+ `)}}if(i)for(const e of i.drainActivities())yield e;yield`data: ${JSON.stringify({type:"done"})}
6
+
7
+ `}async function*x(a){const{phase1:i,phase2:e,skillSet:t}=a;let n=!1;const r=i();for await(const o of d(r.fullStream,t))o.includes('"type":"text"')&&(n=!0),!o.includes('"type":"done"')&&(yield o);if(t.hadFailure&&!n&&t.failureContext){const o=e(t.failureContext);yield*d(o.fullStream)}else yield`data: ${JSON.stringify({type:"done"})}
8
+
9
+ `}exports.createSkillSet=h;exports.streamToWire=d;exports.toSdkMessages=g;exports.withToolRecovery=x;
10
+ //# 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';\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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juneau",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
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
  },