juneau 0.3.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +834 -789
- package/dist/adapters/dashboardAdapter.d.ts.map +1 -1
- package/dist/adapters/mockAdapter.d.ts.map +1 -1
- package/dist/components/AiChat/AiChat.d.ts +8 -1
- package/dist/components/AiChat/AiChat.d.ts.map +1 -1
- package/dist/components/AiMessageBubble/AiMessageBubble.d.ts +4 -1
- package/dist/components/AiMessageBubble/AiMessageBubble.d.ts.map +1 -1
- package/dist/components/AiMessageList/AiMessageList.d.ts +4 -1
- package/dist/components/AiMessageList/AiMessageList.d.ts.map +1 -1
- package/dist/components/AiSidebar/AiSidebar.d.ts +8 -1
- package/dist/components/AiSidebar/AiSidebar.d.ts.map +1 -1
- package/dist/components/parts/AiMessagePartRenderer.d.ts +11 -1
- package/dist/components/parts/AiMessagePartRenderer.d.ts.map +1 -1
- package/dist/core/types.d.ts +14 -4
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.cjs +28 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2635 -2613
- package/dist/index.js.map +1 -1
- package/dist/server/createSkillSet.d.ts +5 -1
- package/dist/server/createSkillSet.d.ts.map +1 -1
- package/dist/server/index.cjs +5 -5
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +62 -57
- package/dist/server/index.js.map +1 -1
- package/dist/server/types.d.ts +16 -1
- package/dist/server/types.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
|
@@ -4,8 +4,12 @@ type SkillMap = Record<string, SkillDefinition<ZodType>>;
|
|
|
4
4
|
/**
|
|
5
5
|
* Takes a map of skill definitions and returns a SkillSet containing:
|
|
6
6
|
* - tools: ready-made tool definitions for ai-sdk's streamText({ tools })
|
|
7
|
-
* - drainActivities(): flush buffered activity wire SSE strings
|
|
7
|
+
* - drainActivities(): flush buffered activity + part wire SSE strings
|
|
8
8
|
* - hadFailure / failureContext: for driving tool failure recovery
|
|
9
|
+
*
|
|
10
|
+
* Each skill's execute fn receives a SkillExecuteContext as its second
|
|
11
|
+
* argument with an `emit(part)` callback — parts are pushed into the same
|
|
12
|
+
* buffer as activities and drained by streamToWire at the same points.
|
|
9
13
|
*/
|
|
10
14
|
export declare function createSkillSet(skills: SkillMap, options?: SkillSetOptions): SkillSet;
|
|
11
15
|
export {};
|
|
@@ -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,
|
|
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,EAAuB,QAAQ,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/F,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;AAUzD;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAE,eAAoB,GAAG,QAAQ,CA0GxF"}
|
package/dist/server/index.cjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function j(i){const s=i.map(n=>{const o=n.parts.filter(e=>e.type==="text").map(e=>e.text).join("");return{role:n.role,content:o}}),r=[];for(let n=0;n<s.length;n++){const o=s[n],e=r[r.length-1];o.role==="user"&&e?.role==="user"&&r.push({role:"assistant",content:"…"}),r.push(o)}return r.filter(n=>n.content.length>0)}function f(i){return`data: ${JSON.stringify(i)}
|
|
2
2
|
|
|
3
|
-
`}function y(i,
|
|
3
|
+
`}function y(i,s){return i[s]??i.en}function x(i,s={}){const r=s.language??"en",n=s.debug??!1,o=[];let e=!1,a=null;const u={};for(const[t,l]of Object.entries(i))u[t]={description:l.description,parameters:l.input,execute:async d=>{n&&console.debug(`[juneau] skill "${t}" → executing, input:`,d);const p=n?Date.now():0;o.push(f({type:"activity",id:t,title:y(l.labels.running,r),status:"running",metadata:{skill:t}}));const T={emit:c=>{n&&console.debug(`[juneau] skill "${t}" → emitting part "${c.type}"`),o.push(f({type:"part",part:c}))}};try{const c=await l.execute(d,T),h=n?Date.now()-p:0;return n&&console.debug(`[juneau] skill "${t}" → done in ${h}ms, result:`,c),o.push(f({type:"activity",id:t,title:y(l.labels.done,r),status:"done",metadata:{skill:t}})),c}catch(c){const h=n?Date.now()-p:0,m=c instanceof Error?c.message:String(c);n&&console.debug(`[juneau] skill "${t}" → FAILED in ${h}ms: "${m}"`);const w=l.labels.failed??{cs:"Nepodařilo se",en:"Failed"};throw o.push(f({type:"activity",id:t,title:y(w,r),status:"failed",metadata:{skill:t}})),e=!0,a=`Tool "${t}" failed: ${m}`,c}}};return{tools:u,drainActivities(){return o.splice(0,o.length)},get hadFailure(){return e},get failureContext(){return a}}}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*g(i,s,r){const n=r?.debug??!1;for await(const o of i){const e=o,a=e.type;if(n)if(a&&b.has(a)){const u=e.text??e.textDelta;console.debug(`[juneau] streamToWire chunk: ${a} ${JSON.stringify(u??"")}`)}else a==="tool-call"?console.debug(`[juneau] streamToWire chunk: tool-call "${e.toolName??e.name??"?"}"`):a==="tool-result"?console.debug("[juneau] streamToWire chunk: tool-result (ignored)"):a&&($.has(a)?console.debug(`[juneau] streamToWire chunk: ${a} (ignored)`):console.debug(`[juneau] streamToWire chunk: ${a} (unknown)`));if(a&&b.has(a)){if(s){const t=s.drainActivities();n&&t.length>0&&console.debug(`[juneau] streamToWire → draining ${t.length} activities`);for(const l of t)yield l}const u=e.text??e.textDelta;u&&(n&&console.debug(`[juneau] streamToWire → yielding text ${JSON.stringify(u)}`),yield`data: ${JSON.stringify({type:"text",text:u})}
|
|
4
4
|
|
|
5
|
-
`)}}if(
|
|
5
|
+
`)}}if(s){const o=s.drainActivities();n&&o.length>0&&console.debug(`[juneau] streamToWire → draining ${o.length} activities (end of stream)`);for(const e of o)yield e}n&&console.debug("[juneau] streamToWire → end of stream, emitting done"),yield`data: ${JSON.stringify({type:"done"})}
|
|
6
6
|
|
|
7
7
|
`}function v(i){return`data: ${JSON.stringify({type:"error",message:i})}
|
|
8
8
|
|
|
9
|
-
`}function
|
|
9
|
+
`}function S(){return`data: ${JSON.stringify({type:"done"})}
|
|
10
10
|
|
|
11
|
-
`}async function*
|
|
11
|
+
`}async function*k(i){const{phase1:s,phase2:r,phase3:n,skillSet:o,debug:e=!1}=i;e&&console.debug("[juneau] withToolRecovery → phase 1 start");let a=!1;const u=s();for await(const t of g(u.fullStream,o,{debug:e}))t.includes('"type":"text"')&&(a=!0),!t.includes('"type":"done"')&&(yield t);if(e&&console.debug(`[juneau] withToolRecovery → phase 1 complete, textProduced=${a}, hadFailure=${o.hadFailure}`),o.hadFailure&&!a&&o.failureContext){e&&console.debug("[juneau] withToolRecovery → hadFailure=true, textProduced=false → triggering phase 2");try{const t=r(o.failureContext);yield*g(t.fullStream,void 0,{debug:e}),e&&console.debug("[juneau] withToolRecovery → phase 2 complete")}catch(t){const l=t instanceof Error?t.message:String(t);e&&console.debug(`[juneau] withToolRecovery → phase 2 FAILED: "${l}"`),yield v(l)}return}if(!a&&!o.hadFailure&&n){const t=await R(u);if(t){e&&(console.debug(`[juneau] withToolRecovery → phase 3 messages resolved (${t.length}):`,JSON.stringify(t,null,2)),N(t)||console.debug("[juneau] withToolRecovery → WARNING: resolved history contains no tool-result turn — Gemini will likely reject it as an invalid conversation structure"),console.debug("[juneau] withToolRecovery → no failure, no text → triggering phase 3"));try{yield*g(n(t).fullStream,void 0,{debug:e}),e&&console.debug("[juneau] withToolRecovery → phase 3 complete")}catch(l){const d=l instanceof Error?l.message:String(l);e&&console.debug(`[juneau] withToolRecovery → phase 3 FAILED: "${d}"`),yield v(d)}return}e&&console.debug("[juneau] withToolRecovery → phase 3 configured but phase 1 result exposes no message history — emitting done")}else!a&&!o.hadFailure&&e&&console.debug("[juneau] withToolRecovery → no failure, no text, no phase3 — emitting done (Gemini silent response)");yield S()}async function R(i){if(i.messages){const s=await i.messages;if(Array.isArray(s)&&s.length>0)return s}if(i.response){const s=await i.response;if(Array.isArray(s?.messages)&&s.messages.length>0)return s.messages}return null}function N(i){return i.some(s=>{const r=s;return r.role==="tool"?!0:Array.isArray(r.content)?r.content.some(n=>n.type==="tool-result"):!1})}exports.createSkillSet=x;exports.streamToWire=g;exports.toSdkMessages=j;exports.withToolRecovery=k;
|
|
12
12
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +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\nfunction wireError(message: string): string {\n return `data: ${JSON.stringify({ type: 'error', message })}\\n\\n`;\n}\n\nfunction wireDone(): string {\n return `data: ${JSON.stringify({ type: 'done' })}\\n\\n`;\n}\n\n/**\n * Encapsulates the multi-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. Collect whether any text was produced and whether\n * a tool failure occurred.\n *\n * Phase 2 (failure recovery) — 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 * Phase 3 (silent-success recovery) — triggers when the tool succeeded but the\n * model wrote no text. Confirmed Gemini 2.5 Flash behaviour: the fullStream ends\n * with tool-call → tool-result → finish-step → finish and no text-delta ever\n * arrives — Gemini treats the tool call as its complete response. Phase 3 receives\n * the full phase 1 message history including tool-call AND tool-result turns —\n * Gemini rejects histories where a tool-call turn is not immediately followed by\n * a tool-result turn (\"function call turn must come immediately after a user turn\n * or after a function response turn\"). The history is resolved from the consumed\n * phase 1 result's `messages` promise (ai-sdk v7 — resolves to all turns after\n * the stream is consumed), with `response.messages` as fallback.\n * If phase3 is not provided, falls back to emitting done (previous behaviour).\n *\n * Errors thrown by phase 2 / phase 3 model calls are surfaced as wire error\n * events — never swallowed into a silent done.\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, phase3, 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 or 3\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 try {\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 } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (debug) {\n console.debug(`[juneau] withToolRecovery → phase 2 FAILED: \"${message}\"`);\n }\n yield wireError(message);\n }\n return;\n }\n\n if (!producedText && !skillSet.hadFailure && phase3) {\n // Tool succeeded but model emitted no text — Gemini silent-response scenario.\n // Resolve the full message history from the consumed phase 1 result and let\n // phase 3 call the model again (no tools) to summarise the tool result.\n const fullMessages = await resolvePhase1Messages(result1);\n\n if (fullMessages) {\n if (debug) {\n console.debug(\n `[juneau] withToolRecovery → phase 3 messages resolved (${fullMessages.length}):`,\n JSON.stringify(fullMessages, null, 2)\n );\n if (!containsToolResult(fullMessages)) {\n console.debug(\n '[juneau] withToolRecovery → WARNING: resolved history contains no tool-result turn — Gemini will likely reject it as an invalid conversation structure'\n );\n }\n console.debug('[juneau] withToolRecovery → no failure, no text → triggering phase 3');\n }\n\n try {\n yield* streamToWire(phase3(fullMessages).fullStream, undefined, { debug });\n\n if (debug) {\n console.debug('[juneau] withToolRecovery → phase 3 complete');\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (debug) {\n console.debug(`[juneau] withToolRecovery → phase 3 FAILED: \"${message}\"`);\n }\n yield wireError(message);\n }\n return;\n }\n\n if (debug) {\n console.debug(\n '[juneau] withToolRecovery → phase 3 configured but phase 1 result exposes no message history — emitting done'\n );\n }\n } else if (!producedText && !skillSet.hadFailure && debug) {\n console.debug(\n '[juneau] withToolRecovery → no failure, no text, no phase3 — emitting done (Gemini silent response)'\n );\n }\n\n yield wireDone();\n}\n\n/**\n * Resolves the full message history (incl. tool-call/tool-result turns) from a\n * consumed ai-sdk StreamTextResult.\n *\n * ai-sdk v7: `result.messages` is the promise that resolves to ALL turns —\n * user, assistant(tool-call), tool(tool-result) — after the stream is consumed.\n * `result.response.messages` may resolve to an incomplete subset (observed:\n * tool-call turn without the following tool-result turn, which Gemini rejects),\n * so it is only used as a fallback when `messages` is unavailable.\n *\n * Both promises resolve only after the fullStream has been fully consumed,\n * which is guaranteed at the call site.\n */\nasync function resolvePhase1Messages(result: {\n messages?: Promise<unknown[]>;\n response?: Promise<{ messages?: unknown[] }>;\n}): Promise<unknown[] | null> {\n if (result.messages) {\n const messages = await result.messages;\n if (Array.isArray(messages) && messages.length > 0) {\n return messages;\n }\n }\n\n if (result.response) {\n const response = await result.response;\n if (Array.isArray(response?.messages) && response.messages.length > 0) {\n return response.messages;\n }\n }\n\n return null;\n}\n\n/**\n * Checks whether the resolved history contains a tool-result turn — either as\n * a `role: 'tool'` message or a content part with `type: 'tool-result'`.\n * Used only for the debug warning; the messages themselves are passed through opaquely.\n */\nfunction containsToolResult(messages: unknown[]): boolean {\n return messages.some((m) => {\n const msg = m as { role?: string; content?: unknown };\n if (msg.role === 'tool') return true;\n if (Array.isArray(msg.content)) {\n return msg.content.some(\n (part) => (part as { type?: string }).type === 'tool-result'\n );\n }\n return false;\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","wireError","wireDone","withToolRecovery","phase1","phase2","phase3","producedText","result1","result2","fullMessages","resolvePhase1Messages","containsToolResult","response","m","part"],"mappings":"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"}
|
|
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';\r\nimport type { SkillDefinition, SkillExecuteContext, SkillSet, SkillSetOptions } from './types';\r\n\r\ntype SkillMap = Record<string, SkillDefinition<ZodType>>;\r\n\r\nfunction formatWireEvent(event: Record<string, unknown>): string {\r\n return `data: ${JSON.stringify(event)}\\n\\n`;\r\n}\r\n\r\nfunction pickLabel(labels: { cs: string; en: string }, language: string): string {\r\n return (labels as Record<string, string>)[language] ?? labels.en;\r\n}\r\n\r\n/**\r\n * Takes a map of skill definitions and returns a SkillSet containing:\r\n * - tools: ready-made tool definitions for ai-sdk's streamText({ tools })\r\n * - drainActivities(): flush buffered activity + part wire SSE strings\r\n * - hadFailure / failureContext: for driving tool failure recovery\r\n *\r\n * Each skill's execute fn receives a SkillExecuteContext as its second\r\n * argument with an `emit(part)` callback — parts are pushed into the same\r\n * buffer as activities and drained by streamToWire at the same points.\r\n */\r\nexport function createSkillSet(skills: SkillMap, options: SkillSetOptions = {}): SkillSet {\r\n const language = options.language ?? 'en';\r\n const debug = options.debug ?? false;\r\n const activityBuffer: string[] = [];\r\n let hadFailure = false;\r\n let failureContext: string | null = null;\r\n\r\n const tools: Record<string, unknown> = {};\r\n\r\n for (const [name, skill] of Object.entries(skills)) {\r\n tools[name] = {\r\n description: skill.description,\r\n parameters: skill.input,\r\n execute: async (input: unknown) => {\r\n if (debug) {\r\n console.debug(`[juneau] skill \"${name}\" → executing, input:`, input);\r\n }\r\n\r\n const startMs = debug ? Date.now() : 0;\r\n\r\n // Emit a \"running\" activity event into the buffer\r\n activityBuffer.push(\r\n formatWireEvent({\r\n type: 'activity',\r\n id: name,\r\n title: pickLabel(skill.labels.running, language),\r\n status: 'running',\r\n metadata: { skill: name },\r\n })\r\n );\r\n\r\n const executeContext: SkillExecuteContext = {\r\n emit: (part) => {\r\n if (debug) {\r\n console.debug(`[juneau] skill \"${name}\" → emitting part \"${part.type}\"`);\r\n }\r\n activityBuffer.push(formatWireEvent({ type: 'part', part }));\r\n },\r\n };\r\n\r\n try {\r\n const result = await skill.execute(input as never, executeContext);\r\n\r\n const durationMs = debug ? Date.now() - startMs : 0;\r\n if (debug) {\r\n console.debug(`[juneau] skill \"${name}\" → done in ${durationMs}ms, result:`, result);\r\n }\r\n\r\n // Emit a \"done\" activity event\r\n activityBuffer.push(\r\n formatWireEvent({\r\n type: 'activity',\r\n id: name,\r\n title: pickLabel(skill.labels.done, language),\r\n status: 'done',\r\n metadata: { skill: name },\r\n })\r\n );\r\n\r\n return result;\r\n } catch (err) {\r\n const durationMs = debug ? Date.now() - startMs : 0;\r\n const message = err instanceof Error ? err.message : String(err);\r\n\r\n if (debug) {\r\n console.debug(`[juneau] skill \"${name}\" → FAILED in ${durationMs}ms: \"${message}\"`);\r\n }\r\n\r\n const failedLabels = skill.labels.failed ?? { cs: 'Nepodařilo se', en: 'Failed' };\r\n\r\n // Emit a \"failed\" activity event\r\n activityBuffer.push(\r\n formatWireEvent({\r\n type: 'activity',\r\n id: name,\r\n title: pickLabel(failedLabels, language),\r\n status: 'failed',\r\n metadata: { skill: name },\r\n })\r\n );\r\n\r\n hadFailure = true;\r\n failureContext = `Tool \"${name}\" failed: ${message}`;\r\n\r\n // Re-throw so ai-sdk knows the tool call failed\r\n throw err;\r\n }\r\n },\r\n };\r\n }\r\n\r\n return {\r\n tools,\r\n\r\n drainActivities(): string[] {\r\n return activityBuffer.splice(0, activityBuffer.length);\r\n },\r\n\r\n get hadFailure() {\r\n return hadFailure;\r\n },\r\n\r\n get failureContext() {\r\n return failureContext;\r\n },\r\n };\r\n}\r\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\nfunction wireError(message: string): string {\n return `data: ${JSON.stringify({ type: 'error', message })}\\n\\n`;\n}\n\nfunction wireDone(): string {\n return `data: ${JSON.stringify({ type: 'done' })}\\n\\n`;\n}\n\n/**\n * Encapsulates the multi-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. Collect whether any text was produced and whether\n * a tool failure occurred.\n *\n * Phase 2 (failure recovery) — 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 * Phase 3 (silent-success recovery) — triggers when the tool succeeded but the\n * model wrote no text. Confirmed Gemini 2.5 Flash behaviour: the fullStream ends\n * with tool-call → tool-result → finish-step → finish and no text-delta ever\n * arrives — Gemini treats the tool call as its complete response. Phase 3 receives\n * the full phase 1 message history including tool-call AND tool-result turns —\n * Gemini rejects histories where a tool-call turn is not immediately followed by\n * a tool-result turn (\"function call turn must come immediately after a user turn\n * or after a function response turn\"). The history is resolved from the consumed\n * phase 1 result's `messages` promise (ai-sdk v7 — resolves to all turns after\n * the stream is consumed), with `response.messages` as fallback.\n * If phase3 is not provided, falls back to emitting done (previous behaviour).\n *\n * Errors thrown by phase 2 / phase 3 model calls are surfaced as wire error\n * events — never swallowed into a silent done.\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, phase3, 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 or 3\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 try {\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 } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (debug) {\n console.debug(`[juneau] withToolRecovery → phase 2 FAILED: \"${message}\"`);\n }\n yield wireError(message);\n }\n return;\n }\n\n if (!producedText && !skillSet.hadFailure && phase3) {\n // Tool succeeded but model emitted no text — Gemini silent-response scenario.\n // Resolve the full message history from the consumed phase 1 result and let\n // phase 3 call the model again (no tools) to summarise the tool result.\n const fullMessages = await resolvePhase1Messages(result1);\n\n if (fullMessages) {\n if (debug) {\n console.debug(\n `[juneau] withToolRecovery → phase 3 messages resolved (${fullMessages.length}):`,\n JSON.stringify(fullMessages, null, 2)\n );\n if (!containsToolResult(fullMessages)) {\n console.debug(\n '[juneau] withToolRecovery → WARNING: resolved history contains no tool-result turn — Gemini will likely reject it as an invalid conversation structure'\n );\n }\n console.debug('[juneau] withToolRecovery → no failure, no text → triggering phase 3');\n }\n\n try {\n yield* streamToWire(phase3(fullMessages).fullStream, undefined, { debug });\n\n if (debug) {\n console.debug('[juneau] withToolRecovery → phase 3 complete');\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (debug) {\n console.debug(`[juneau] withToolRecovery → phase 3 FAILED: \"${message}\"`);\n }\n yield wireError(message);\n }\n return;\n }\n\n if (debug) {\n console.debug(\n '[juneau] withToolRecovery → phase 3 configured but phase 1 result exposes no message history — emitting done'\n );\n }\n } else if (!producedText && !skillSet.hadFailure && debug) {\n console.debug(\n '[juneau] withToolRecovery → no failure, no text, no phase3 — emitting done (Gemini silent response)'\n );\n }\n\n yield wireDone();\n}\n\n/**\n * Resolves the full message history (incl. tool-call/tool-result turns) from a\n * consumed ai-sdk StreamTextResult.\n *\n * ai-sdk v7: `result.messages` is the promise that resolves to ALL turns —\n * user, assistant(tool-call), tool(tool-result) — after the stream is consumed.\n * `result.response.messages` may resolve to an incomplete subset (observed:\n * tool-call turn without the following tool-result turn, which Gemini rejects),\n * so it is only used as a fallback when `messages` is unavailable.\n *\n * Both promises resolve only after the fullStream has been fully consumed,\n * which is guaranteed at the call site.\n */\nasync function resolvePhase1Messages(result: {\n messages?: Promise<unknown[]>;\n response?: Promise<{ messages?: unknown[] }>;\n}): Promise<unknown[] | null> {\n if (result.messages) {\n const messages = await result.messages;\n if (Array.isArray(messages) && messages.length > 0) {\n return messages;\n }\n }\n\n if (result.response) {\n const response = await result.response;\n if (Array.isArray(response?.messages) && response.messages.length > 0) {\n return response.messages;\n }\n }\n\n return null;\n}\n\n/**\n * Checks whether the resolved history contains a tool-result turn — either as\n * a `role: 'tool'` message or a content part with `type: 'tool-result'`.\n * Used only for the debug warning; the messages themselves are passed through opaquely.\n */\nfunction containsToolResult(messages: unknown[]): boolean {\n return messages.some((m) => {\n const msg = m as { role?: string; content?: unknown };\n if (msg.role === 'tool') return true;\n if (Array.isArray(msg.content)) {\n return msg.content.some(\n (part) => (part as { type?: string }).type === 'tool-result'\n );\n }\n return false;\n });\n}\n"],"names":["toSdkMessages","messages","mapped","msg","text","p","result","i","current","prev","formatWireEvent","event","pickLabel","labels","language","createSkillSet","skills","options","debug","activityBuffer","hadFailure","failureContext","tools","name","skill","input","startMs","executeContext","part","durationMs","err","message","failedLabels","TEXT_CHUNK_TYPES","IGNORED_CHUNK_TYPES","streamToWire","fullStream","skillSet","chunk","c","type","activities","remaining","wireError","wireDone","withToolRecovery","phase1","phase2","phase3","producedText","result1","result2","fullMessages","resolvePhase1Messages","containsToolResult","response","m"],"mappings":"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"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -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, ToolRecoveryStreamResult, StreamToWireOptions, CoreMessage } from './types';
|
|
5
|
+
export type { SkillSet, SkillDefinition, SkillExecuteContext, SkillSetOptions, ToolRecoveryOptions, ToolRecoveryStreamResult, 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,wBAAwB,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,mBAAmB,EAAE,eAAe,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/server/index.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
function
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
return { role: n.role, content:
|
|
1
|
+
function k(i) {
|
|
2
|
+
const s = i.map((n) => {
|
|
3
|
+
const o = n.parts.filter((e) => e.type === "text").map((e) => e.text).join("");
|
|
4
|
+
return { role: n.role, content: o };
|
|
5
5
|
}), r = [];
|
|
6
|
-
for (let n = 0; n <
|
|
7
|
-
const
|
|
8
|
-
|
|
6
|
+
for (let n = 0; n < s.length; n++) {
|
|
7
|
+
const o = s[n], e = r[r.length - 1];
|
|
8
|
+
o.role === "user" && e?.role === "user" && r.push({ role: "assistant", content: "…" }), r.push(o);
|
|
9
9
|
}
|
|
10
10
|
return r.filter((n) => n.content.length > 0);
|
|
11
11
|
}
|
|
12
|
-
function
|
|
12
|
+
function f(i) {
|
|
13
13
|
return `data: ${JSON.stringify(i)}
|
|
14
14
|
|
|
15
15
|
`;
|
|
16
16
|
}
|
|
17
|
-
function h(i,
|
|
18
|
-
return i[
|
|
17
|
+
function h(i, s) {
|
|
18
|
+
return i[s] ?? i.en;
|
|
19
19
|
}
|
|
20
|
-
function
|
|
21
|
-
const r =
|
|
20
|
+
function R(i, s = {}) {
|
|
21
|
+
const r = s.language ?? "en", n = s.debug ?? !1, o = [];
|
|
22
22
|
let e = !1, a = null;
|
|
23
23
|
const u = {};
|
|
24
24
|
for (const [t, l] of Object.entries(i))
|
|
@@ -28,8 +28,8 @@ function k(i, o = {}) {
|
|
|
28
28
|
execute: async (d) => {
|
|
29
29
|
n && console.debug(`[juneau] skill "${t}" → executing, input:`, d);
|
|
30
30
|
const p = n ? Date.now() : 0;
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
o.push(
|
|
32
|
+
f({
|
|
33
33
|
type: "activity",
|
|
34
34
|
id: t,
|
|
35
35
|
title: h(l.labels.running, r),
|
|
@@ -37,10 +37,15 @@ function k(i, o = {}) {
|
|
|
37
37
|
metadata: { skill: t }
|
|
38
38
|
})
|
|
39
39
|
);
|
|
40
|
+
const x = {
|
|
41
|
+
emit: (c) => {
|
|
42
|
+
n && console.debug(`[juneau] skill "${t}" → emitting part "${c.type}"`), o.push(f({ type: "part", part: c }));
|
|
43
|
+
}
|
|
44
|
+
};
|
|
40
45
|
try {
|
|
41
|
-
const c = await l.execute(d),
|
|
42
|
-
return n && console.debug(`[juneau] skill "${t}" → done in ${
|
|
43
|
-
|
|
46
|
+
const c = await l.execute(d, x), g = n ? Date.now() - p : 0;
|
|
47
|
+
return n && console.debug(`[juneau] skill "${t}" → done in ${g}ms, result:`, c), o.push(
|
|
48
|
+
f({
|
|
44
49
|
type: "activity",
|
|
45
50
|
id: t,
|
|
46
51
|
title: h(l.labels.done, r),
|
|
@@ -49,11 +54,11 @@ function k(i, o = {}) {
|
|
|
49
54
|
})
|
|
50
55
|
), c;
|
|
51
56
|
} catch (c) {
|
|
52
|
-
const
|
|
53
|
-
n && console.debug(`[juneau] skill "${t}" → FAILED in ${
|
|
57
|
+
const g = n ? Date.now() - p : 0, m = c instanceof Error ? c.message : String(c);
|
|
58
|
+
n && console.debug(`[juneau] skill "${t}" → FAILED in ${g}ms: "${m}"`);
|
|
54
59
|
const T = l.labels.failed ?? { cs: "Nepodařilo se", en: "Failed" };
|
|
55
|
-
throw
|
|
56
|
-
|
|
60
|
+
throw o.push(
|
|
61
|
+
f({
|
|
57
62
|
type: "activity",
|
|
58
63
|
id: t,
|
|
59
64
|
title: h(T, r),
|
|
@@ -67,7 +72,7 @@ function k(i, o = {}) {
|
|
|
67
72
|
return {
|
|
68
73
|
tools: u,
|
|
69
74
|
drainActivities() {
|
|
70
|
-
return
|
|
75
|
+
return o.splice(0, o.length);
|
|
71
76
|
},
|
|
72
77
|
get hadFailure() {
|
|
73
78
|
return e;
|
|
@@ -91,18 +96,18 @@ const b = /* @__PURE__ */ new Set(["text-delta", "text"]), w = /* @__PURE__ */ n
|
|
|
91
96
|
"response-metadata",
|
|
92
97
|
"stream-start"
|
|
93
98
|
]);
|
|
94
|
-
async function* y(i,
|
|
99
|
+
async function* y(i, s, r) {
|
|
95
100
|
const n = r?.debug ?? !1;
|
|
96
|
-
for await (const
|
|
97
|
-
const e =
|
|
101
|
+
for await (const o of i) {
|
|
102
|
+
const e = o, a = e.type;
|
|
98
103
|
if (n)
|
|
99
104
|
if (a && b.has(a)) {
|
|
100
105
|
const u = e.text ?? e.textDelta;
|
|
101
106
|
console.debug(`[juneau] streamToWire chunk: ${a} ${JSON.stringify(u ?? "")}`);
|
|
102
107
|
} else a === "tool-call" ? console.debug(`[juneau] streamToWire chunk: tool-call "${e.toolName ?? e.name ?? "?"}"`) : a === "tool-result" ? console.debug("[juneau] streamToWire chunk: tool-result (ignored)") : a && (w.has(a) ? console.debug(`[juneau] streamToWire chunk: ${a} (ignored)`) : console.debug(`[juneau] streamToWire chunk: ${a} (unknown)`));
|
|
103
108
|
if (a && b.has(a)) {
|
|
104
|
-
if (
|
|
105
|
-
const t =
|
|
109
|
+
if (s) {
|
|
110
|
+
const t = s.drainActivities();
|
|
106
111
|
n && t.length > 0 && console.debug(`[juneau] streamToWire → draining ${t.length} activities`);
|
|
107
112
|
for (const l of t)
|
|
108
113
|
yield l;
|
|
@@ -113,10 +118,10 @@ async function* y(i, o, r) {
|
|
|
113
118
|
`);
|
|
114
119
|
}
|
|
115
120
|
}
|
|
116
|
-
if (
|
|
117
|
-
const
|
|
118
|
-
n &&
|
|
119
|
-
for (const e of
|
|
121
|
+
if (s) {
|
|
122
|
+
const o = s.drainActivities();
|
|
123
|
+
n && o.length > 0 && console.debug(`[juneau] streamToWire → draining ${o.length} activities (end of stream)`);
|
|
124
|
+
for (const e of o)
|
|
120
125
|
yield e;
|
|
121
126
|
}
|
|
122
127
|
n && console.debug("[juneau] streamToWire → end of stream, emitting done"), yield `data: ${JSON.stringify({ type: "done" })}
|
|
@@ -128,24 +133,24 @@ function v(i) {
|
|
|
128
133
|
|
|
129
134
|
`;
|
|
130
135
|
}
|
|
131
|
-
function
|
|
136
|
+
function j() {
|
|
132
137
|
return `data: ${JSON.stringify({ type: "done" })}
|
|
133
138
|
|
|
134
139
|
`;
|
|
135
140
|
}
|
|
136
|
-
async function*
|
|
137
|
-
const { phase1:
|
|
141
|
+
async function* N(i) {
|
|
142
|
+
const { phase1: s, phase2: r, phase3: n, skillSet: o, debug: e = !1 } = i;
|
|
138
143
|
e && console.debug("[juneau] withToolRecovery → phase 1 start");
|
|
139
144
|
let a = !1;
|
|
140
|
-
const u =
|
|
141
|
-
for await (const t of y(u.fullStream,
|
|
145
|
+
const u = s();
|
|
146
|
+
for await (const t of y(u.fullStream, o, { debug: e }))
|
|
142
147
|
t.includes('"type":"text"') && (a = !0), !t.includes('"type":"done"') && (yield t);
|
|
143
148
|
if (e && console.debug(
|
|
144
|
-
`[juneau] withToolRecovery → phase 1 complete, textProduced=${a}, hadFailure=${
|
|
145
|
-
),
|
|
149
|
+
`[juneau] withToolRecovery → phase 1 complete, textProduced=${a}, hadFailure=${o.hadFailure}`
|
|
150
|
+
), o.hadFailure && !a && o.failureContext) {
|
|
146
151
|
e && console.debug("[juneau] withToolRecovery → hadFailure=true, textProduced=false → triggering phase 2");
|
|
147
152
|
try {
|
|
148
|
-
const t = r(
|
|
153
|
+
const t = r(o.failureContext);
|
|
149
154
|
yield* y(t.fullStream, void 0, { debug: e }), e && console.debug("[juneau] withToolRecovery → phase 2 complete");
|
|
150
155
|
} catch (t) {
|
|
151
156
|
const l = t instanceof Error ? t.message : String(t);
|
|
@@ -153,13 +158,13 @@ async function* R(i) {
|
|
|
153
158
|
}
|
|
154
159
|
return;
|
|
155
160
|
}
|
|
156
|
-
if (!a && !
|
|
157
|
-
const t = await
|
|
161
|
+
if (!a && !o.hadFailure && n) {
|
|
162
|
+
const t = await $(u);
|
|
158
163
|
if (t) {
|
|
159
164
|
e && (console.debug(
|
|
160
165
|
`[juneau] withToolRecovery → phase 3 messages resolved (${t.length}):`,
|
|
161
166
|
JSON.stringify(t, null, 2)
|
|
162
|
-
),
|
|
167
|
+
), S(t) || console.debug(
|
|
163
168
|
"[juneau] withToolRecovery → WARNING: resolved history contains no tool-result turn — Gemini will likely reject it as an invalid conversation structure"
|
|
164
169
|
), console.debug("[juneau] withToolRecovery → no failure, no text → triggering phase 3"));
|
|
165
170
|
try {
|
|
@@ -173,36 +178,36 @@ async function* R(i) {
|
|
|
173
178
|
e && console.debug(
|
|
174
179
|
"[juneau] withToolRecovery → phase 3 configured but phase 1 result exposes no message history — emitting done"
|
|
175
180
|
);
|
|
176
|
-
} else !a && !
|
|
181
|
+
} else !a && !o.hadFailure && e && console.debug(
|
|
177
182
|
"[juneau] withToolRecovery → no failure, no text, no phase3 — emitting done (Gemini silent response)"
|
|
178
183
|
);
|
|
179
|
-
yield
|
|
184
|
+
yield j();
|
|
180
185
|
}
|
|
181
|
-
async function
|
|
186
|
+
async function $(i) {
|
|
182
187
|
if (i.messages) {
|
|
183
|
-
const
|
|
184
|
-
if (Array.isArray(
|
|
185
|
-
return
|
|
188
|
+
const s = await i.messages;
|
|
189
|
+
if (Array.isArray(s) && s.length > 0)
|
|
190
|
+
return s;
|
|
186
191
|
}
|
|
187
192
|
if (i.response) {
|
|
188
|
-
const
|
|
189
|
-
if (Array.isArray(
|
|
190
|
-
return
|
|
193
|
+
const s = await i.response;
|
|
194
|
+
if (Array.isArray(s?.messages) && s.messages.length > 0)
|
|
195
|
+
return s.messages;
|
|
191
196
|
}
|
|
192
197
|
return null;
|
|
193
198
|
}
|
|
194
|
-
function
|
|
195
|
-
return i.some((
|
|
196
|
-
const r =
|
|
199
|
+
function S(i) {
|
|
200
|
+
return i.some((s) => {
|
|
201
|
+
const r = s;
|
|
197
202
|
return r.role === "tool" ? !0 : Array.isArray(r.content) ? r.content.some(
|
|
198
203
|
(n) => n.type === "tool-result"
|
|
199
204
|
) : !1;
|
|
200
205
|
});
|
|
201
206
|
}
|
|
202
207
|
export {
|
|
203
|
-
|
|
208
|
+
R as createSkillSet,
|
|
204
209
|
y as streamToWire,
|
|
205
|
-
|
|
206
|
-
|
|
210
|
+
k as toSdkMessages,
|
|
211
|
+
N as withToolRecovery
|
|
207
212
|
};
|
|
208
213
|
//# sourceMappingURL=index.js.map
|
package/dist/server/index.js.map
CHANGED
|
@@ -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';\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\nfunction wireError(message: string): string {\n return `data: ${JSON.stringify({ type: 'error', message })}\\n\\n`;\n}\n\nfunction wireDone(): string {\n return `data: ${JSON.stringify({ type: 'done' })}\\n\\n`;\n}\n\n/**\n * Encapsulates the multi-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. Collect whether any text was produced and whether\n * a tool failure occurred.\n *\n * Phase 2 (failure recovery) — 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 * Phase 3 (silent-success recovery) — triggers when the tool succeeded but the\n * model wrote no text. Confirmed Gemini 2.5 Flash behaviour: the fullStream ends\n * with tool-call → tool-result → finish-step → finish and no text-delta ever\n * arrives — Gemini treats the tool call as its complete response. Phase 3 receives\n * the full phase 1 message history including tool-call AND tool-result turns —\n * Gemini rejects histories where a tool-call turn is not immediately followed by\n * a tool-result turn (\"function call turn must come immediately after a user turn\n * or after a function response turn\"). The history is resolved from the consumed\n * phase 1 result's `messages` promise (ai-sdk v7 — resolves to all turns after\n * the stream is consumed), with `response.messages` as fallback.\n * If phase3 is not provided, falls back to emitting done (previous behaviour).\n *\n * Errors thrown by phase 2 / phase 3 model calls are surfaced as wire error\n * events — never swallowed into a silent done.\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, phase3, 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 or 3\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 try {\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 } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (debug) {\n console.debug(`[juneau] withToolRecovery → phase 2 FAILED: \"${message}\"`);\n }\n yield wireError(message);\n }\n return;\n }\n\n if (!producedText && !skillSet.hadFailure && phase3) {\n // Tool succeeded but model emitted no text — Gemini silent-response scenario.\n // Resolve the full message history from the consumed phase 1 result and let\n // phase 3 call the model again (no tools) to summarise the tool result.\n const fullMessages = await resolvePhase1Messages(result1);\n\n if (fullMessages) {\n if (debug) {\n console.debug(\n `[juneau] withToolRecovery → phase 3 messages resolved (${fullMessages.length}):`,\n JSON.stringify(fullMessages, null, 2)\n );\n if (!containsToolResult(fullMessages)) {\n console.debug(\n '[juneau] withToolRecovery → WARNING: resolved history contains no tool-result turn — Gemini will likely reject it as an invalid conversation structure'\n );\n }\n console.debug('[juneau] withToolRecovery → no failure, no text → triggering phase 3');\n }\n\n try {\n yield* streamToWire(phase3(fullMessages).fullStream, undefined, { debug });\n\n if (debug) {\n console.debug('[juneau] withToolRecovery → phase 3 complete');\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (debug) {\n console.debug(`[juneau] withToolRecovery → phase 3 FAILED: \"${message}\"`);\n }\n yield wireError(message);\n }\n return;\n }\n\n if (debug) {\n console.debug(\n '[juneau] withToolRecovery → phase 3 configured but phase 1 result exposes no message history — emitting done'\n );\n }\n } else if (!producedText && !skillSet.hadFailure && debug) {\n console.debug(\n '[juneau] withToolRecovery → no failure, no text, no phase3 — emitting done (Gemini silent response)'\n );\n }\n\n yield wireDone();\n}\n\n/**\n * Resolves the full message history (incl. tool-call/tool-result turns) from a\n * consumed ai-sdk StreamTextResult.\n *\n * ai-sdk v7: `result.messages` is the promise that resolves to ALL turns —\n * user, assistant(tool-call), tool(tool-result) — after the stream is consumed.\n * `result.response.messages` may resolve to an incomplete subset (observed:\n * tool-call turn without the following tool-result turn, which Gemini rejects),\n * so it is only used as a fallback when `messages` is unavailable.\n *\n * Both promises resolve only after the fullStream has been fully consumed,\n * which is guaranteed at the call site.\n */\nasync function resolvePhase1Messages(result: {\n messages?: Promise<unknown[]>;\n response?: Promise<{ messages?: unknown[] }>;\n}): Promise<unknown[] | null> {\n if (result.messages) {\n const messages = await result.messages;\n if (Array.isArray(messages) && messages.length > 0) {\n return messages;\n }\n }\n\n if (result.response) {\n const response = await result.response;\n if (Array.isArray(response?.messages) && response.messages.length > 0) {\n return response.messages;\n }\n }\n\n return null;\n}\n\n/**\n * Checks whether the resolved history contains a tool-result turn — either as\n * a `role: 'tool'` message or a content part with `type: 'tool-result'`.\n * Used only for the debug warning; the messages themselves are passed through opaquely.\n */\nfunction containsToolResult(messages: unknown[]): boolean {\n return messages.some((m) => {\n const msg = m as { role?: string; content?: unknown };\n if (msg.role === 'tool') return true;\n if (Array.isArray(msg.content)) {\n return msg.content.some(\n (part) => (part as { type?: string }).type === 'tool-result'\n );\n }\n return false;\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","wireError","wireDone","withToolRecovery","phase1","phase2","phase3","producedText","result1","result2","fullMessages","resolvePhase1Messages","containsToolResult","response","m","part"],"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';\r\nimport type { SkillDefinition, SkillExecuteContext, SkillSet, SkillSetOptions } from './types';\r\n\r\ntype SkillMap = Record<string, SkillDefinition<ZodType>>;\r\n\r\nfunction formatWireEvent(event: Record<string, unknown>): string {\r\n return `data: ${JSON.stringify(event)}\\n\\n`;\r\n}\r\n\r\nfunction pickLabel(labels: { cs: string; en: string }, language: string): string {\r\n return (labels as Record<string, string>)[language] ?? labels.en;\r\n}\r\n\r\n/**\r\n * Takes a map of skill definitions and returns a SkillSet containing:\r\n * - tools: ready-made tool definitions for ai-sdk's streamText({ tools })\r\n * - drainActivities(): flush buffered activity + part wire SSE strings\r\n * - hadFailure / failureContext: for driving tool failure recovery\r\n *\r\n * Each skill's execute fn receives a SkillExecuteContext as its second\r\n * argument with an `emit(part)` callback — parts are pushed into the same\r\n * buffer as activities and drained by streamToWire at the same points.\r\n */\r\nexport function createSkillSet(skills: SkillMap, options: SkillSetOptions = {}): SkillSet {\r\n const language = options.language ?? 'en';\r\n const debug = options.debug ?? false;\r\n const activityBuffer: string[] = [];\r\n let hadFailure = false;\r\n let failureContext: string | null = null;\r\n\r\n const tools: Record<string, unknown> = {};\r\n\r\n for (const [name, skill] of Object.entries(skills)) {\r\n tools[name] = {\r\n description: skill.description,\r\n parameters: skill.input,\r\n execute: async (input: unknown) => {\r\n if (debug) {\r\n console.debug(`[juneau] skill \"${name}\" → executing, input:`, input);\r\n }\r\n\r\n const startMs = debug ? Date.now() : 0;\r\n\r\n // Emit a \"running\" activity event into the buffer\r\n activityBuffer.push(\r\n formatWireEvent({\r\n type: 'activity',\r\n id: name,\r\n title: pickLabel(skill.labels.running, language),\r\n status: 'running',\r\n metadata: { skill: name },\r\n })\r\n );\r\n\r\n const executeContext: SkillExecuteContext = {\r\n emit: (part) => {\r\n if (debug) {\r\n console.debug(`[juneau] skill \"${name}\" → emitting part \"${part.type}\"`);\r\n }\r\n activityBuffer.push(formatWireEvent({ type: 'part', part }));\r\n },\r\n };\r\n\r\n try {\r\n const result = await skill.execute(input as never, executeContext);\r\n\r\n const durationMs = debug ? Date.now() - startMs : 0;\r\n if (debug) {\r\n console.debug(`[juneau] skill \"${name}\" → done in ${durationMs}ms, result:`, result);\r\n }\r\n\r\n // Emit a \"done\" activity event\r\n activityBuffer.push(\r\n formatWireEvent({\r\n type: 'activity',\r\n id: name,\r\n title: pickLabel(skill.labels.done, language),\r\n status: 'done',\r\n metadata: { skill: name },\r\n })\r\n );\r\n\r\n return result;\r\n } catch (err) {\r\n const durationMs = debug ? Date.now() - startMs : 0;\r\n const message = err instanceof Error ? err.message : String(err);\r\n\r\n if (debug) {\r\n console.debug(`[juneau] skill \"${name}\" → FAILED in ${durationMs}ms: \"${message}\"`);\r\n }\r\n\r\n const failedLabels = skill.labels.failed ?? { cs: 'Nepodařilo se', en: 'Failed' };\r\n\r\n // Emit a \"failed\" activity event\r\n activityBuffer.push(\r\n formatWireEvent({\r\n type: 'activity',\r\n id: name,\r\n title: pickLabel(failedLabels, language),\r\n status: 'failed',\r\n metadata: { skill: name },\r\n })\r\n );\r\n\r\n hadFailure = true;\r\n failureContext = `Tool \"${name}\" failed: ${message}`;\r\n\r\n // Re-throw so ai-sdk knows the tool call failed\r\n throw err;\r\n }\r\n },\r\n };\r\n }\r\n\r\n return {\r\n tools,\r\n\r\n drainActivities(): string[] {\r\n return activityBuffer.splice(0, activityBuffer.length);\r\n },\r\n\r\n get hadFailure() {\r\n return hadFailure;\r\n },\r\n\r\n get failureContext() {\r\n return failureContext;\r\n },\r\n };\r\n}\r\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\nfunction wireError(message: string): string {\n return `data: ${JSON.stringify({ type: 'error', message })}\\n\\n`;\n}\n\nfunction wireDone(): string {\n return `data: ${JSON.stringify({ type: 'done' })}\\n\\n`;\n}\n\n/**\n * Encapsulates the multi-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. Collect whether any text was produced and whether\n * a tool failure occurred.\n *\n * Phase 2 (failure recovery) — 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 * Phase 3 (silent-success recovery) — triggers when the tool succeeded but the\n * model wrote no text. Confirmed Gemini 2.5 Flash behaviour: the fullStream ends\n * with tool-call → tool-result → finish-step → finish and no text-delta ever\n * arrives — Gemini treats the tool call as its complete response. Phase 3 receives\n * the full phase 1 message history including tool-call AND tool-result turns —\n * Gemini rejects histories where a tool-call turn is not immediately followed by\n * a tool-result turn (\"function call turn must come immediately after a user turn\n * or after a function response turn\"). The history is resolved from the consumed\n * phase 1 result's `messages` promise (ai-sdk v7 — resolves to all turns after\n * the stream is consumed), with `response.messages` as fallback.\n * If phase3 is not provided, falls back to emitting done (previous behaviour).\n *\n * Errors thrown by phase 2 / phase 3 model calls are surfaced as wire error\n * events — never swallowed into a silent done.\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, phase3, 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 or 3\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 try {\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 } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (debug) {\n console.debug(`[juneau] withToolRecovery → phase 2 FAILED: \"${message}\"`);\n }\n yield wireError(message);\n }\n return;\n }\n\n if (!producedText && !skillSet.hadFailure && phase3) {\n // Tool succeeded but model emitted no text — Gemini silent-response scenario.\n // Resolve the full message history from the consumed phase 1 result and let\n // phase 3 call the model again (no tools) to summarise the tool result.\n const fullMessages = await resolvePhase1Messages(result1);\n\n if (fullMessages) {\n if (debug) {\n console.debug(\n `[juneau] withToolRecovery → phase 3 messages resolved (${fullMessages.length}):`,\n JSON.stringify(fullMessages, null, 2)\n );\n if (!containsToolResult(fullMessages)) {\n console.debug(\n '[juneau] withToolRecovery → WARNING: resolved history contains no tool-result turn — Gemini will likely reject it as an invalid conversation structure'\n );\n }\n console.debug('[juneau] withToolRecovery → no failure, no text → triggering phase 3');\n }\n\n try {\n yield* streamToWire(phase3(fullMessages).fullStream, undefined, { debug });\n\n if (debug) {\n console.debug('[juneau] withToolRecovery → phase 3 complete');\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (debug) {\n console.debug(`[juneau] withToolRecovery → phase 3 FAILED: \"${message}\"`);\n }\n yield wireError(message);\n }\n return;\n }\n\n if (debug) {\n console.debug(\n '[juneau] withToolRecovery → phase 3 configured but phase 1 result exposes no message history — emitting done'\n );\n }\n } else if (!producedText && !skillSet.hadFailure && debug) {\n console.debug(\n '[juneau] withToolRecovery → no failure, no text, no phase3 — emitting done (Gemini silent response)'\n );\n }\n\n yield wireDone();\n}\n\n/**\n * Resolves the full message history (incl. tool-call/tool-result turns) from a\n * consumed ai-sdk StreamTextResult.\n *\n * ai-sdk v7: `result.messages` is the promise that resolves to ALL turns —\n * user, assistant(tool-call), tool(tool-result) — after the stream is consumed.\n * `result.response.messages` may resolve to an incomplete subset (observed:\n * tool-call turn without the following tool-result turn, which Gemini rejects),\n * so it is only used as a fallback when `messages` is unavailable.\n *\n * Both promises resolve only after the fullStream has been fully consumed,\n * which is guaranteed at the call site.\n */\nasync function resolvePhase1Messages(result: {\n messages?: Promise<unknown[]>;\n response?: Promise<{ messages?: unknown[] }>;\n}): Promise<unknown[] | null> {\n if (result.messages) {\n const messages = await result.messages;\n if (Array.isArray(messages) && messages.length > 0) {\n return messages;\n }\n }\n\n if (result.response) {\n const response = await result.response;\n if (Array.isArray(response?.messages) && response.messages.length > 0) {\n return response.messages;\n }\n }\n\n return null;\n}\n\n/**\n * Checks whether the resolved history contains a tool-result turn — either as\n * a `role: 'tool'` message or a content part with `type: 'tool-result'`.\n * Used only for the debug warning; the messages themselves are passed through opaquely.\n */\nfunction containsToolResult(messages: unknown[]): boolean {\n return messages.some((m) => {\n const msg = m as { role?: string; content?: unknown };\n if (msg.role === 'tool') return true;\n if (Array.isArray(msg.content)) {\n return msg.content.some(\n (part) => (part as { type?: string }).type === 'tool-result'\n );\n }\n return false;\n });\n}\n"],"names":["toSdkMessages","messages","mapped","msg","text","p","result","i","current","prev","formatWireEvent","event","pickLabel","labels","language","createSkillSet","skills","options","debug","activityBuffer","hadFailure","failureContext","tools","name","skill","input","startMs","executeContext","part","durationMs","err","message","failedLabels","TEXT_CHUNK_TYPES","IGNORED_CHUNK_TYPES","streamToWire","fullStream","skillSet","chunk","c","type","activities","remaining","wireError","wireDone","withToolRecovery","phase1","phase2","phase3","producedText","result1","result2","fullMessages","resolvePhase1Messages","containsToolResult","response","m"],"mappings":"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"}
|
package/dist/server/types.d.ts
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import type { ZodType, z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Helpers injected into a skill's execute fn as the second argument.
|
|
4
|
+
*/
|
|
5
|
+
export interface SkillExecuteContext {
|
|
6
|
+
/**
|
|
7
|
+
* Emits a custom part wire event directly into the stream, alongside the
|
|
8
|
+
* automatic running/done/failed activities. Available synchronously at any
|
|
9
|
+
* point during execution — parts appear in the stream in emit order.
|
|
10
|
+
* The part is wrapped as { type: 'part', part } on the wire.
|
|
11
|
+
*/
|
|
12
|
+
emit: (part: {
|
|
13
|
+
type: string;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
}) => void;
|
|
16
|
+
}
|
|
2
17
|
/**
|
|
3
18
|
* A single skill definition — description, zod input schema, i18n labels, and the execute fn.
|
|
4
19
|
*/
|
|
@@ -19,7 +34,7 @@ export interface SkillDefinition<TInput extends ZodType = ZodType> {
|
|
|
19
34
|
en: string;
|
|
20
35
|
};
|
|
21
36
|
};
|
|
22
|
-
execute: (input: z.infer<TInput
|
|
37
|
+
execute: (input: z.infer<TInput>, context: SkillExecuteContext) => Promise<unknown>;
|
|
23
38
|
}
|
|
24
39
|
/**
|
|
25
40
|
* The object returned by createSkillSet.
|
|
@@ -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;
|
|
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,mBAAmB;IAClC;;;;;OAKG;IACH,IAAI,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,KAAK,IAAI,CAAC;CAChE;AAED;;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,EAAE,OAAO,EAAE,mBAAmB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACrF;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;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;KAAE,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,wBAAwB,CAAC;IACvC,MAAM,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK;QAAE,UAAU,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC;IAC3E;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK;QAAE,UAAU,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC;IACzE,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"}
|