@lunora/agent 1.0.0-alpha.57 → 1.0.0-alpha.59
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/dist/channels.d.mts +2 -2
- package/dist/channels.d.ts +2 -2
- package/dist/component.mjs +1 -1
- package/dist/inbound.d.mts +1 -1
- package/dist/inbound.d.ts +1 -1
- package/dist/index.d.mts +34 -4
- package/dist/index.d.ts +34 -4
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/{VoiceSessionDO-CZG-WkQ8.mjs → VoiceSessionDO-A6u5NXI1.mjs} +1 -1
- package/dist/packem_shared/adaptMcpResult-CVlq-_TO.mjs +2 -0
- package/dist/packem_shared/codeTool-B1_HVhnJ.mjs +1 -0
- package/dist/packem_shared/{compileAgentWorkflow-O_Kvw_Ze.mjs → compileAgentWorkflow-BvkRUzx_.mjs} +1 -1
- package/dist/packem_shared/{createAgentContext-HUKAccII.mjs → createAgentContext-CfD3hIZ2.mjs} +1 -1
- package/dist/packem_shared/createDispatchRunner-CGafifJ7-C__lmjdK.mjs +1 -0
- package/dist/packem_shared/functionTool-DbrqDql3.mjs +1 -0
- package/dist/packem_shared/{runAgentLoop-k63A5u8S.mjs → runAgentLoop-6fGTajbG.mjs} +2 -2
- package/dist/packem_shared/sandboxComponent-BtOkUYpY.mjs +5 -0
- package/dist/packem_shared/{types.d-CeO8cn_u.d.mts → types.d-BoMo2eJj.d.mts} +46 -15
- package/dist/packem_shared/{types.d-CeO8cn_u.d.ts → types.d-BoMo2eJj.d.ts} +46 -15
- package/dist/reply.d.mts +1 -1
- package/dist/reply.d.ts +1 -1
- package/dist/sandbox.d.mts +13 -12
- package/dist/sandbox.d.ts +13 -12
- package/dist/sandbox.mjs +1 -1
- package/dist/skill-markdown.d.mts +1 -1
- package/dist/skill-markdown.d.ts +1 -1
- package/package.json +4 -4
- package/dist/packem_shared/adaptMcpResult-D90rkUqS.mjs +0 -2
- package/dist/packem_shared/codeTool-B1SSluGg.mjs +0 -1
- package/dist/packem_shared/createDispatchRunner-DIjJ2U-O-C5xqijhC.mjs +0 -1
- package/dist/packem_shared/functionTool-CaWLukmr.mjs +0 -1
- package/dist/packem_shared/sandboxComponent-DcvkZjNW.mjs +0 -1
package/dist/channels.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as AgentDefinition } from "./packem_shared/types.d-
|
|
2
|
-
export type { I as InboundChannelEvent } from "./packem_shared/types.d-
|
|
1
|
+
import { A as AgentDefinition } from "./packem_shared/types.d-BoMo2eJj.mjs";
|
|
2
|
+
export type { I as InboundChannelEvent } from "./packem_shared/types.d-BoMo2eJj.mjs";
|
|
3
3
|
import '@lunora/mail/inbound';
|
|
4
4
|
import 'ai';
|
|
5
5
|
/**
|
package/dist/channels.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as AgentDefinition } from "./packem_shared/types.d-
|
|
2
|
-
export type { I as InboundChannelEvent } from "./packem_shared/types.d-
|
|
1
|
+
import { A as AgentDefinition } from "./packem_shared/types.d-BoMo2eJj.js";
|
|
2
|
+
export type { I as InboundChannelEvent } from "./packem_shared/types.d-BoMo2eJj.js";
|
|
3
3
|
import '@lunora/mail/inbound';
|
|
4
4
|
import 'ai';
|
|
5
5
|
/**
|
package/dist/component.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{LunoraError as I}from"@lunora/errors";import{defineTable as v,initLunora as S,defineSchemaExtension as B}from"@lunora/server";import{v as e}from"@lunora/values";import{a as f,A as k,d as j}from"./packem_shared/component-shared-Cn6E5d_q.mjs";import{graphTables as P,graphComponent as F}from"./packem_shared/normalizeEntityName-bz1Thnmp.mjs";import{normalizeEntityName as ce}from"./packem_shared/normalizeEntityName-bz1Thnmp.mjs";import{sandboxComponent as pe}from"./packem_shared/sandboxComponent-
|
|
1
|
+
import{LunoraError as I}from"@lunora/errors";import{defineTable as v,initLunora as S,defineSchemaExtension as B}from"@lunora/server";import{v as e}from"@lunora/values";import{a as f,A as k,d as j}from"./packem_shared/component-shared-Cn6E5d_q.mjs";import{graphTables as P,graphComponent as F}from"./packem_shared/normalizeEntityName-bz1Thnmp.mjs";import{normalizeEntityName as ce}from"./packem_shared/normalizeEntityName-bz1Thnmp.mjs";import{sandboxComponent as pe}from"./packem_shared/sandboxComponent-BtOkUYpY.mjs";const K="agent_episodes",G=5,H=20,X=500,x=200,Q=/\s+/gu,Y=u=>u===void 0||!Number.isFinite(u)?G:Math.min(H,Math.max(1,Math.trunc(u))),z={episodes:v({createdAt:e.number(),messageKey:e.string(),owner:e.string(),summary:e.string(),threadKey:e.optional(e.string())}).index("byOwnerCreatedAt",["owner","createdAt"]).index("byOwnerMessageKey",["owner","messageKey"],{unique:!0}).public()},{mutation:W,query:J}=S.dataModel().create(),V=()=>{const u=W.input({createdAt:e.optional(e.number()),messageKey:e.string(),owner:e.string(),summary:e.string(),threadKey:e.optional(e.string())}).mutation(async({args:d,ctx:c})=>{const m=d.summary.replaceAll(Q," ").trim().slice(0,X);if(m.length===0)return{recorded:!1};if(await c.db.query(K).withIndex("byOwnerMessageKey",w=>w.eq("owner",d.owner).eq("messageKey",d.messageKey)).first())return{recorded:!1};await c.db.insert(K,{createdAt:d.createdAt??Date.now(),messageKey:d.messageKey,owner:d.owner,summary:m,...d.threadKey===void 0?{}:{threadKey:d.threadKey}});const b=await c.db.query(K).withIndex("byOwnerCreatedAt",w=>w.eq("owner",d.owner)).order("asc").take(x+1);for(const w of b.slice(0,Math.max(0,b.length-x)))await c.db.delete(w._id);return{recorded:!0}}),o=J.input({limit:e.optional(e.number()),owner:e.string()}).query(async({args:d,ctx:c})=>{const m=await c.db.query(K).withIndex("byOwnerCreatedAt",y=>y.eq("owner",d.owner)).order("desc").take(Y(d.limit));return m.length===0?{context:""}:{context:m.toReversed().map(y=>`- ${y.summary}`).join(`
|
|
2
2
|
`)}});return{agentEpisodeRecall:f(o),agentEpisodeUpsert:f(u)}},$="threads",D="messages",M="run_queue",h=`${k}_${$}`,A=`${k}_${D}`,q=`${k}_${M}`,N=5,Z=780*60*1e3,ee=B(k,{tables:{[D]:v({content:e.string(),createdAt:e.number(),messageKey:e.string(),role:e.union(e.literal("user"),e.literal("assistant"),e.literal("tool"),e.literal("system")),seq:e.number(),status:e.optional(e.union(e.literal("awaiting_approval"),e.literal("approved"),e.literal("rejected"))),stepName:e.optional(e.string()),threadKey:e.string(),toolCallId:e.optional(e.string()),toolCalls:e.optional(e.array(e.object({id:e.string(),input:e.any(),name:e.string()}))),toolName:e.optional(e.string())}).index("byThread",["threadKey","seq"]).index("byMessageKey",["threadKey","messageKey"],{unique:!0}).public(),[$]:v({agent:e.string(),createdAt:e.number(),error:e.optional(e.string()),instanceId:e.optional(e.string()),key:e.string(),messageCount:e.number(),owner:e.optional(e.string()),state:e.optional(e.any()),status:e.union(e.literal("idle"),e.literal("running"),e.literal("error"),e.literal("cancelled"),e.literal("awaiting_input")),title:e.optional(e.string()),updatedAt:e.number(),usage:e.optional(e.object({inputTokens:e.optional(e.number()),outputTokens:e.optional(e.number()),totalTokens:e.optional(e.number())}))}).index("byKey",["key"],{unique:!0}).index("byAgent",["agent"]).index("byInstance",["instanceId"]).public(),[M]:v({agent:e.string(),enqueuedAt:e.number(),instanceId:e.string(),position:e.number(),threadKey:e.string()}).index("byThread",["threadKey","position"]).index("byThreadInstance",["threadKey","instanceId"],{unique:!0}).public(),...P,...z}}),{mutation:g,query:_}=S.dataModel().create(),te=async(u,o,d)=>{if(o.instanceId===void 0)throw new I("CONFLICT",`@lunora/agent: thread "${o.key}" already has a run in flight (instance "${o.priorInstanceId}") — cannot queue a dispatch with no instance id`);const{instanceId:c}=o,m=await u.query(q).withIndex("byThreadInstance",w=>w.eq("threadKey",o.key).eq("instanceId",c)).first();if(m)return{outcome:"queued",position:m.position};const y=await u.query(q).withIndex("byThread",w=>w.eq("threadKey",o.key)).collect();if(y.length>=N)throw new I("CONFLICT",`@lunora/agent: thread "${o.key}" run queue is full (depth ${String(N)}) — rejecting instance "${c}"`);const b=(y.at(-1)?.position??-1)+1;return await u.insert(q,{agent:o.agent,enqueuedAt:d,instanceId:c,position:b,threadKey:o.key}),{outcome:"queued",position:b}},ne=async(u,o,d)=>{if(o.policy==="queue")return te(u,{agent:o.agent,instanceId:o.instanceId,key:o.key,priorInstanceId:o.priorInstanceId},d);if(o.policy!=="replace")throw new I("CONFLICT",`@lunora/agent: thread "${o.key}" already has a run in flight (instance "${o.priorInstanceId}") — onConcurrentRun="${o.policy}"`);return await u.patch(o.existingId,{error:void 0,status:"running",updatedAt:d,...o.instanceId===void 0?{}:{instanceId:o.instanceId}}),{outcome:"replaced",priorInstanceId:o.priorInstanceId}},se=()=>{const u=g.input({agent:e.string(),initialState:e.optional(e.any()),instanceId:e.optional(e.string()),key:e.string(),onConcurrentRun:e.optional(e.union(e.literal("reject"),e.literal("queue"),e.literal("replace"))),owner:e.optional(e.string()),title:e.optional(e.string())}).mutation(async({args:t,ctx:n})=>{const a=Date.now(),i=await n.db.query(h).withIndex("byKey",r=>r.eq("key",t.key)).first();if(i){if(i.owner!==t.owner&&t.owner!==void 0)throw new Error(`@lunora/agent: thread "${t.key}" belongs to another owner`);const r=i.instanceId,s=typeof i.updatedAt=="number"?i.updatedAt:0;return!(a-s>Z)&&(i.status==="running"||i.status==="awaiting_input")&&r!==void 0&&(t.instanceId===void 0||t.instanceId!==r)?ne(n.db,{agent:t.agent,existingId:i._id,instanceId:t.instanceId,key:t.key,policy:t.onConcurrentRun??"reject",priorInstanceId:r},a):(await n.db.patch(i._id,{error:void 0,status:"running",updatedAt:a,...t.instanceId===void 0?{}:{instanceId:t.instanceId}}),{outcome:"continued"})}return await n.db.insert(h,{agent:t.agent,createdAt:a,key:t.key,messageCount:0,status:"running",updatedAt:a,...j({instanceId:t.instanceId,owner:t.owner,state:t.initialState,title:t.title})}),{outcome:"created"}}),o=g.input({content:e.string(),messageKey:e.string(),role:e.union(e.literal("user"),e.literal("assistant"),e.literal("tool"),e.literal("system")),status:e.optional(e.union(e.literal("awaiting_approval"),e.literal("approved"),e.literal("rejected"))),stepName:e.optional(e.string()),threadKey:e.string(),toolCallId:e.optional(e.string()),toolCalls:e.optional(e.array(e.object({id:e.string(),input:e.any(),name:e.string()}))),toolName:e.optional(e.string())}).mutation(async({args:t,ctx:n})=>{const a=await n.db.query(A).withIndex("byMessageKey",p=>p.eq("threadKey",t.threadKey).eq("messageKey",t.messageKey)).first();if(a)return{seq:a.seq};const i=await n.db.query(h).withIndex("byKey",p=>p.eq("key",t.threadKey)).first();if(!i)throw new Error(`@lunora/agent: cannot append to unknown thread "${t.threadKey}" — run agentEnsureThread first`);const r=i.messageCount,s=Date.now();return await n.db.insert(A,{content:t.content,createdAt:s,messageKey:t.messageKey,role:t.role,seq:r,threadKey:t.threadKey,...t.status===void 0?{}:{status:t.status},...t.stepName===void 0?{}:{stepName:t.stepName},...t.toolCallId===void 0?{}:{toolCallId:t.toolCallId},...t.toolCalls===void 0?{}:{toolCalls:t.toolCalls},...t.toolName===void 0?{}:{toolName:t.toolName}}),await n.db.patch(i._id,{messageCount:r+1,updatedAt:s}),{seq:r}}),d=g.input({error:e.optional(e.string()),instanceId:e.optional(e.string()),key:e.optional(e.string()),status:e.optional(e.union(e.literal("idle"),e.literal("running"),e.literal("error"),e.literal("cancelled"),e.literal("awaiting_input"))),title:e.optional(e.string()),usage:e.optional(e.object({inputTokens:e.optional(e.number()),outputTokens:e.optional(e.number()),totalTokens:e.optional(e.number())}))}).mutation(async({args:t,ctx:n})=>{const{instanceId:a,key:i}=t;let r;i!==void 0?r=await n.db.query(h).withIndex("byKey",s=>s.eq("key",i)).first():a!==void 0&&(r=await n.db.query(h).withIndex("byInstance",s=>s.eq("instanceId",a)).first()),r&&await n.db.patch(r._id,{updatedAt:Date.now(),...t.error===void 0?{}:{error:t.error},...t.status===void 0?{}:{status:t.status},...t.title===void 0?{}:{title:t.title},...t.usage===void 0?{}:{usage:t.usage}})}),c=g.input({error:e.optional(e.string()),instanceId:e.string(),key:e.string(),status:e.union(e.literal("idle"),e.literal("error"),e.literal("cancelled")),usage:e.optional(e.object({inputTokens:e.optional(e.number()),outputTokens:e.optional(e.number()),totalTokens:e.optional(e.number())}))}).mutation(async({args:t,ctx:n})=>{const a=Date.now(),i=await n.db.query(h).withIndex("byKey",l=>l.eq("key",t.key)).first();if(i?.instanceId!==t.instanceId){const l=await n.db.query(q).withIndex("byThreadInstance",E=>E.eq("threadKey",t.key).eq("instanceId",t.instanceId)).first();return l&&await n.db.delete(l._id),{}}const s=(await n.db.query(q).withIndex("byThread",l=>l.eq("threadKey",t.key)).collect())[0];if(!s)return await n.db.patch(i._id,{status:t.status,updatedAt:a,...t.error===void 0?{}:{error:t.error},...t.usage===void 0?{}:{usage:t.usage}}),{};const p=s.instanceId;return await n.db.delete(s._id),await n.db.patch(i._id,{instanceId:p,status:"running",updatedAt:a,...t.error===void 0?{}:{error:t.error},...t.usage===void 0?{}:{usage:t.usage}}),{dequeued:p}}),m=g.input({key:e.string(),state:e.any()}).mutation(async({args:t,ctx:n})=>{const a=await n.db.query(h).withIndex("byKey",i=>i.eq("key",t.key)).first();a&&await n.db.patch(a._id,{state:t.state,updatedAt:Date.now()})}),y=(t,n)=>{if(!t)return;const{owner:a}=t;if(!(a!==void 0&&a!==(n.userId??void 0)))return t},b=_.input({key:e.string()}).query(async({args:t,ctx:n})=>{const a=await n.db.query(h).withIndex("byKey",i=>i.eq("key",t.key)).first();return y(a,n.auth)}),w=_.input({key:e.string()}).query(async({args:t,ctx:n})=>{const a=await n.db.query(h).withIndex("byKey",i=>i.eq("key",t.key)).first();return y(a,n.auth)?.state}),O=_.input({key:e.string(),limit:e.optional(e.number())}).query(async({args:t,ctx:n})=>{const a=await n.db.query(h).withIndex("byKey",i=>i.eq("key",t.key)).first();return y(a,n.auth)===void 0?[]:t.limit!==void 0?(await n.db.query(A).withIndex("byThread",r=>r.eq("threadKey",t.key)).order("desc").take(t.limit)).toReversed():n.db.query(A).withIndex("byThread",i=>i.eq("threadKey",t.key)).collect()}),L=g.input({decision:e.union(e.literal("approve"),e.literal("reject")),instanceId:e.string(),note:e.optional(e.string()),threadKey:e.string(),toolCallId:e.string()}).mutation(async({args:t,ctx:n})=>{const a=await n.db.query(h).withIndex("byKey",l=>l.eq("key",t.threadKey)).first(),i=y(a,n.auth);if(i===void 0)throw new I("FORBIDDEN",`@lunora/agent: not allowed to resolve approvals on thread "${t.threadKey}"`);if(i.instanceId!==t.instanceId)throw new I("FORBIDDEN",`@lunora/agent: instance "${t.instanceId}" does not own thread "${t.threadKey}"`);const r=i.agent,{agents:s}=n,p=s?.[r];if(typeof p?.sendEvent!="function")throw new I("INTERNAL",`@lunora/agent: no ctx.agents["${r}"] producer to resolve the approval — run codegen/dev so the agent binding is wired`);return await p.sendEvent(t.instanceId,{payload:{decision:t.decision,toolCallId:t.toolCallId,...t.note===void 0?{}:{note:t.note}},type:`agent-approval:${t.toolCallId}`}),{resolved:!0}}),U=g.input({agent:e.string(),input:e.string(),threadKey:e.string(),title:e.optional(e.string())}).mutation(async({args:t,ctx:n})=>{const{agents:a}=n,i=a?.[t.agent];if(typeof i?.run!="function")throw new I("INTERNAL",`@lunora/agent: no ctx.agents["${t.agent}"] producer to start a run — run codegen/dev so the agent binding is wired, and check the agent name`);if(i.publicRun!==!0)throw new I("FORBIDDEN",`@lunora/agent: agent "${t.agent}" is not enabled for public runs — set defineAgent({ publicRun: true }) to allow an external client (e.g. the @lunora/mcp server) to start it`);const r=n.auth.userId??void 0,s=await n.db.query(h).withIndex("byKey",l=>l.eq("key",t.threadKey)).first();if(s){const l=s.status,E=s.instanceId,T=s.owner;if((l==="running"||l==="awaiting_input")&&E!==void 0&&(T===void 0||T===r))return{id:E,threadKey:t.threadKey};if(T!==r&&r!==void 0)throw new I("FORBIDDEN",`@lunora/agent: thread "${t.threadKey}" belongs to another owner`)}const{id:p}=await i.run({input:t.input,threadKey:t.threadKey,...r===void 0?{}:{owner:r},...t.title===void 0?{}:{title:t.title}});return{id:p,threadKey:t.threadKey}}),C=F(),R=V();return{extension:ee,functions:{agentAppendMessage:f(o),agentCompleteRun:f(c),agentEnsureThread:f(u),agentEpisodeRecall:R.agentEpisodeRecall,agentEpisodeUpsert:R.agentEpisodeUpsert,agentGraphTraverse:C.agentGraphTraverse,agentGraphUpsert:C.agentGraphUpsert,agentMessages:O,agentPatchThread:f(d),agentResolveApproval:L,agentRun:U,agentSetState:f(m),agentState:w,agentThread:b}}};export{se as agentComponent,ee as agentExtension,ce as normalizeEntityName,pe as sandboxComponent};
|
package/dist/inbound.d.mts
CHANGED
package/dist/inbound.d.ts
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as AgentDefinition, e as AgentCompact, f as AgentEpisodeExtract, g as AgentGraphExtract, h as AgentGenerate, i as AgentTokenSink, j as AgentRunInput, k as AgentFunctionPaths, l as AgentRunFunction, m as AgentStepLike, n as AgentStreamGenerate, o as AgentRunResult, p as AgentMessageRow, q as AgentConfig, d as SkillDefinition, r as AnyAgentTool, s as AgentAsToolOptions, t as AgentSubToolInput, c as AgentToolDefinition, u as AgentBindingSpec, v as AgentHandle, w as AgentToolConfig, x as AgentFunctionReference, y as AgentModelInput, z as SkillConfig } from "./packem_shared/types.d-
|
|
2
|
-
export type { B as AgentApprovalContext, C as AgentEmailMapper, D as AgentEmailRun, E as AgentGenerateOptions, F as AgentGenerateResult, G as AgentInstructionsContext, H as AgentLiveEvent, J as AgentMemoryOptions, K as AgentMemorySource, L as AgentMessageStatus, M as AgentOnReply, N as AgentOnStepFinish, O as AgentPrepareStep, P as AgentPrepareStepInput, Q as AgentPrepareStepResult, R as AgentProgressEvent, T as AgentReplyInfo, a as AgentReplyRef, U as AgentRunHandle, V as AgentStepFinishInfo, W as AgentStepInfo, X as AgentThreadStatus, Y as AgentTokenDelta, Z as AgentToolCall, b as AgentToolContext, _ as AgentUsage, $ as AgentVoiceConfig, a0 as AgentWorkflowBindingLike, a1 as AgentWorkflowInstanceLike, a2 as EnsureThreadOutcome } from "./packem_shared/types.d-
|
|
1
|
+
import { A as AgentDefinition, e as AgentCompact, f as AgentEpisodeExtract, g as AgentGraphExtract, h as AgentGenerate, i as AgentTokenSink, j as AgentRunInput, k as AgentFunctionPaths, l as AgentRunFunction, m as AgentStepLike, n as AgentStreamGenerate, o as AgentRunResult, p as AgentMessageRow, q as AgentConfig, d as SkillDefinition, r as AnyAgentTool, s as AgentAsToolOptions, t as AgentSubToolInput, c as AgentToolDefinition, u as AgentBindingSpec, v as AgentHandle, w as AgentToolConfig, x as AgentFunctionReference, y as AgentModelInput, z as SkillConfig } from "./packem_shared/types.d-BoMo2eJj.mjs";
|
|
2
|
+
export type { B as AgentApprovalContext, C as AgentEmailMapper, D as AgentEmailRun, E as AgentGenerateOptions, F as AgentGenerateResult, G as AgentInstructionsContext, H as AgentLiveEvent, J as AgentMemoryOptions, K as AgentMemorySource, L as AgentMessageStatus, M as AgentOnReply, N as AgentOnStepFinish, O as AgentPrepareStep, P as AgentPrepareStepInput, Q as AgentPrepareStepResult, R as AgentProgressEvent, T as AgentReplyInfo, a as AgentReplyRef, U as AgentRunHandle, V as AgentStepFinishInfo, W as AgentStepInfo, X as AgentThreadStatus, Y as AgentTokenDelta, Z as AgentToolCall, b as AgentToolContext, _ as AgentUsage, $ as AgentVoiceConfig, a0 as AgentWorkflowBindingLike, a1 as AgentWorkflowInstanceLike, a2 as EnsureThreadOutcome } from "./packem_shared/types.d-BoMo2eJj.mjs";
|
|
3
3
|
export { type AgentComponent, agentComponent, agentExtension } from "./component.mjs";
|
|
4
4
|
import { FlexibleSchema, LanguageModel, ModelMessage } from 'ai';
|
|
5
5
|
export { agentBindingName, agentClassName, agentDefaultName, voiceBindingName, voiceClassName } from "./naming.mjs";
|
|
@@ -239,6 +239,16 @@ interface CodeToolOptions {
|
|
|
239
239
|
* keep approval-gated tools as normal top-level tools. Gate the whole script via
|
|
240
240
|
* `opts.needsApproval` instead.
|
|
241
241
|
*
|
|
242
|
+
* STEP-OUTPUT CONTRACT — **every step's output must be JSON-serializable.** Each
|
|
243
|
+
* step runs inside its own durable `step.do`, so the workflow host serializes
|
|
244
|
+
* the returned value before the next step (or the tool result) ever sees it. A
|
|
245
|
+
* `Date` comes back as an ISO string, a `Map`/`Set` as `{}`, `undefined` as
|
|
246
|
+
* `null` or a dropped key, and a `bigint` throws outright. That applies to the
|
|
247
|
+
* value a later step reads through `{ "$from": … }` just as much as to the
|
|
248
|
+
* returned `results` — the hand-off is durable state, not an in-memory one. A
|
|
249
|
+
* composed tool that wants to pass a rich value should return its JSON form
|
|
250
|
+
* (`date.toISOString()`, `[...map]`) and let the consuming step rebuild it.
|
|
251
|
+
*
|
|
242
252
|
* ```ts
|
|
243
253
|
* import { codeTool, defineAgent, functionTool } from "@lunora/agent";
|
|
244
254
|
*
|
|
@@ -344,8 +354,14 @@ interface FunctionToolOptions<Input> {
|
|
|
344
354
|
* ```
|
|
345
355
|
*
|
|
346
356
|
* The function reference may be a typed `api.*` reference or a `"module:name"`
|
|
347
|
-
* path. The model-provided input is passed
|
|
348
|
-
* `inputSchema` should mirror the referenced function's argument validator
|
|
357
|
+
* path. The model-provided input is passed as the function's args, so
|
|
358
|
+
* `inputSchema` should mirror the referenced function's argument validator —
|
|
359
|
+
* PLUS the call's `idempotencyKey`, pinned onto the args after the model
|
|
360
|
+
* input (so out-of-schema model input can never override it). A function
|
|
361
|
+
* that wants to dedupe a side-effecting call across an at-least-once step
|
|
362
|
+
* retry declares `idempotencyKey: v.optional(v.string())` in its own args
|
|
363
|
+
* and checks it; a function that ignores it is unaffected — an args field
|
|
364
|
+
* the function's own validator doesn't declare is dropped, not rejected.
|
|
349
365
|
* @experimental
|
|
350
366
|
*/
|
|
351
367
|
declare const functionTool: <Input extends Record<string, unknown> = Record<string, unknown>, Output = unknown>(reference: AgentFunctionReference | string, options: FunctionToolOptions<Input>) => AgentToolDefinition<Input, Output>;
|
|
@@ -471,6 +487,17 @@ interface McpToolsOptions {
|
|
|
471
487
|
command?: string;
|
|
472
488
|
/** Identify this client to the server on connect. Default `"lunora-agent"`. */
|
|
473
489
|
name?: string;
|
|
490
|
+
/**
|
|
491
|
+
* Gate an adapted tool's call behind a human approval. Defaults to
|
|
492
|
+
* **unattended** (no gate) — matching today's behavior. An MCP tool's
|
|
493
|
+
* result feeds back into the model's context (`adaptMcpResult`), so a
|
|
494
|
+
* hostile downstream server can pair indirect prompt injection with an
|
|
495
|
+
* unattended mutating tool call; pass a boolean to gate every adapted
|
|
496
|
+
* tool the same way, or a predicate `(toolName, input) => boolean` to
|
|
497
|
+
* gate per tool (evaluated from replay-stable input, so keep it
|
|
498
|
+
* deterministic).
|
|
499
|
+
*/
|
|
500
|
+
needsApproval?: ((toolName: string, input: Record<string, unknown>) => boolean) | boolean;
|
|
474
501
|
/** Only adapt these tool names (default: every tool the server lists). */
|
|
475
502
|
only?: ReadonlyArray<string>;
|
|
476
503
|
/** Prefix added to each adapted tool's key (disambiguate multiple servers). */
|
|
@@ -510,6 +537,9 @@ declare const adaptMcpResult: (result: McpCallResult) => unknown;
|
|
|
510
537
|
* every call. In the Workers runtime only the HTTP/SSE transports run (stdio
|
|
511
538
|
* does not) — pass `url`, or inject an already-connected `client` for a custom
|
|
512
539
|
* transport (also the test seam).
|
|
540
|
+
*
|
|
541
|
+
* Every adapted tool runs UNATTENDED by default (no approval gate) — pass
|
|
542
|
+
* `opts.needsApproval` to change that; see {@link McpToolsOptions.needsApproval}.
|
|
513
543
|
* @experimental
|
|
514
544
|
*/
|
|
515
545
|
declare const mcpTools: (options: McpToolsOptions) => Promise<Record<string, AgentToolDefinition<Record<string, unknown>>>>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as AgentDefinition, e as AgentCompact, f as AgentEpisodeExtract, g as AgentGraphExtract, h as AgentGenerate, i as AgentTokenSink, j as AgentRunInput, k as AgentFunctionPaths, l as AgentRunFunction, m as AgentStepLike, n as AgentStreamGenerate, o as AgentRunResult, p as AgentMessageRow, q as AgentConfig, d as SkillDefinition, r as AnyAgentTool, s as AgentAsToolOptions, t as AgentSubToolInput, c as AgentToolDefinition, u as AgentBindingSpec, v as AgentHandle, w as AgentToolConfig, x as AgentFunctionReference, y as AgentModelInput, z as SkillConfig } from "./packem_shared/types.d-
|
|
2
|
-
export type { B as AgentApprovalContext, C as AgentEmailMapper, D as AgentEmailRun, E as AgentGenerateOptions, F as AgentGenerateResult, G as AgentInstructionsContext, H as AgentLiveEvent, J as AgentMemoryOptions, K as AgentMemorySource, L as AgentMessageStatus, M as AgentOnReply, N as AgentOnStepFinish, O as AgentPrepareStep, P as AgentPrepareStepInput, Q as AgentPrepareStepResult, R as AgentProgressEvent, T as AgentReplyInfo, a as AgentReplyRef, U as AgentRunHandle, V as AgentStepFinishInfo, W as AgentStepInfo, X as AgentThreadStatus, Y as AgentTokenDelta, Z as AgentToolCall, b as AgentToolContext, _ as AgentUsage, $ as AgentVoiceConfig, a0 as AgentWorkflowBindingLike, a1 as AgentWorkflowInstanceLike, a2 as EnsureThreadOutcome } from "./packem_shared/types.d-
|
|
1
|
+
import { A as AgentDefinition, e as AgentCompact, f as AgentEpisodeExtract, g as AgentGraphExtract, h as AgentGenerate, i as AgentTokenSink, j as AgentRunInput, k as AgentFunctionPaths, l as AgentRunFunction, m as AgentStepLike, n as AgentStreamGenerate, o as AgentRunResult, p as AgentMessageRow, q as AgentConfig, d as SkillDefinition, r as AnyAgentTool, s as AgentAsToolOptions, t as AgentSubToolInput, c as AgentToolDefinition, u as AgentBindingSpec, v as AgentHandle, w as AgentToolConfig, x as AgentFunctionReference, y as AgentModelInput, z as SkillConfig } from "./packem_shared/types.d-BoMo2eJj.js";
|
|
2
|
+
export type { B as AgentApprovalContext, C as AgentEmailMapper, D as AgentEmailRun, E as AgentGenerateOptions, F as AgentGenerateResult, G as AgentInstructionsContext, H as AgentLiveEvent, J as AgentMemoryOptions, K as AgentMemorySource, L as AgentMessageStatus, M as AgentOnReply, N as AgentOnStepFinish, O as AgentPrepareStep, P as AgentPrepareStepInput, Q as AgentPrepareStepResult, R as AgentProgressEvent, T as AgentReplyInfo, a as AgentReplyRef, U as AgentRunHandle, V as AgentStepFinishInfo, W as AgentStepInfo, X as AgentThreadStatus, Y as AgentTokenDelta, Z as AgentToolCall, b as AgentToolContext, _ as AgentUsage, $ as AgentVoiceConfig, a0 as AgentWorkflowBindingLike, a1 as AgentWorkflowInstanceLike, a2 as EnsureThreadOutcome } from "./packem_shared/types.d-BoMo2eJj.js";
|
|
3
3
|
export { type AgentComponent, agentComponent, agentExtension } from "./component.js";
|
|
4
4
|
import { FlexibleSchema, LanguageModel, ModelMessage } from 'ai';
|
|
5
5
|
export { agentBindingName, agentClassName, agentDefaultName, voiceBindingName, voiceClassName } from "./naming.js";
|
|
@@ -239,6 +239,16 @@ interface CodeToolOptions {
|
|
|
239
239
|
* keep approval-gated tools as normal top-level tools. Gate the whole script via
|
|
240
240
|
* `opts.needsApproval` instead.
|
|
241
241
|
*
|
|
242
|
+
* STEP-OUTPUT CONTRACT — **every step's output must be JSON-serializable.** Each
|
|
243
|
+
* step runs inside its own durable `step.do`, so the workflow host serializes
|
|
244
|
+
* the returned value before the next step (or the tool result) ever sees it. A
|
|
245
|
+
* `Date` comes back as an ISO string, a `Map`/`Set` as `{}`, `undefined` as
|
|
246
|
+
* `null` or a dropped key, and a `bigint` throws outright. That applies to the
|
|
247
|
+
* value a later step reads through `{ "$from": … }` just as much as to the
|
|
248
|
+
* returned `results` — the hand-off is durable state, not an in-memory one. A
|
|
249
|
+
* composed tool that wants to pass a rich value should return its JSON form
|
|
250
|
+
* (`date.toISOString()`, `[...map]`) and let the consuming step rebuild it.
|
|
251
|
+
*
|
|
242
252
|
* ```ts
|
|
243
253
|
* import { codeTool, defineAgent, functionTool } from "@lunora/agent";
|
|
244
254
|
*
|
|
@@ -344,8 +354,14 @@ interface FunctionToolOptions<Input> {
|
|
|
344
354
|
* ```
|
|
345
355
|
*
|
|
346
356
|
* The function reference may be a typed `api.*` reference or a `"module:name"`
|
|
347
|
-
* path. The model-provided input is passed
|
|
348
|
-
* `inputSchema` should mirror the referenced function's argument validator
|
|
357
|
+
* path. The model-provided input is passed as the function's args, so
|
|
358
|
+
* `inputSchema` should mirror the referenced function's argument validator —
|
|
359
|
+
* PLUS the call's `idempotencyKey`, pinned onto the args after the model
|
|
360
|
+
* input (so out-of-schema model input can never override it). A function
|
|
361
|
+
* that wants to dedupe a side-effecting call across an at-least-once step
|
|
362
|
+
* retry declares `idempotencyKey: v.optional(v.string())` in its own args
|
|
363
|
+
* and checks it; a function that ignores it is unaffected — an args field
|
|
364
|
+
* the function's own validator doesn't declare is dropped, not rejected.
|
|
349
365
|
* @experimental
|
|
350
366
|
*/
|
|
351
367
|
declare const functionTool: <Input extends Record<string, unknown> = Record<string, unknown>, Output = unknown>(reference: AgentFunctionReference | string, options: FunctionToolOptions<Input>) => AgentToolDefinition<Input, Output>;
|
|
@@ -471,6 +487,17 @@ interface McpToolsOptions {
|
|
|
471
487
|
command?: string;
|
|
472
488
|
/** Identify this client to the server on connect. Default `"lunora-agent"`. */
|
|
473
489
|
name?: string;
|
|
490
|
+
/**
|
|
491
|
+
* Gate an adapted tool's call behind a human approval. Defaults to
|
|
492
|
+
* **unattended** (no gate) — matching today's behavior. An MCP tool's
|
|
493
|
+
* result feeds back into the model's context (`adaptMcpResult`), so a
|
|
494
|
+
* hostile downstream server can pair indirect prompt injection with an
|
|
495
|
+
* unattended mutating tool call; pass a boolean to gate every adapted
|
|
496
|
+
* tool the same way, or a predicate `(toolName, input) => boolean` to
|
|
497
|
+
* gate per tool (evaluated from replay-stable input, so keep it
|
|
498
|
+
* deterministic).
|
|
499
|
+
*/
|
|
500
|
+
needsApproval?: ((toolName: string, input: Record<string, unknown>) => boolean) | boolean;
|
|
474
501
|
/** Only adapt these tool names (default: every tool the server lists). */
|
|
475
502
|
only?: ReadonlyArray<string>;
|
|
476
503
|
/** Prefix added to each adapted tool's key (disambiguate multiple servers). */
|
|
@@ -510,6 +537,9 @@ declare const adaptMcpResult: (result: McpCallResult) => unknown;
|
|
|
510
537
|
* every call. In the Workers runtime only the HTTP/SSE transports run (stdio
|
|
511
538
|
* does not) — pass `url`, or inject an already-connected `client` for a custom
|
|
512
539
|
* transport (also the test seam).
|
|
540
|
+
*
|
|
541
|
+
* Every adapted tool runs UNATTENDED by default (no approval gate) — pass
|
|
542
|
+
* `opts.needsApproval` to change that; see {@link McpToolsOptions.needsApproval}.
|
|
513
543
|
* @experimental
|
|
514
544
|
*/
|
|
515
545
|
declare const mcpTools: (options: McpToolsOptions) => Promise<Record<string, AgentToolDefinition<Record<string, unknown>>>>;
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{runAgentLoop as t,splitForCompaction as r}from"./packem_shared/runAgentLoop-
|
|
1
|
+
import{runAgentLoop as t,splitForCompaction as r}from"./packem_shared/runAgentLoop-6fGTajbG.mjs";import{collectAgenticMemoryTools as a,toSearchResults as i}from"./packem_shared/collectAgenticMemoryTools-UE8gWzQU.mjs";import{agentAsTool as l}from"./packem_shared/agentAsTool-D5iLpvYU.mjs";import{codeTool as p}from"./packem_shared/codeTool-B1_HVhnJ.mjs";import{agentComponent as c,agentExtension as x}from"./component.mjs";import{default as A}from"./packem_shared/createAgentContext-CfD3hIZ2.mjs";import{defineAgent as d,defineAgentTool as u,isAgentDefinition as N}from"./packem_shared/defineAgent-Bqj9exe-.mjs";import{functionTool as D}from"./packem_shared/functionTool-DbrqDql3.mjs";import{createAgentGenerate as M,createEpisodeExtract as O,createGraphExtract as _,createStreamGenerate as C,resolveAgentModel as G}from"./packem_shared/createAgentGenerate-9bZ2YhpN.mjs";import{adaptMcpResult as F,mcpTools as L}from"./packem_shared/adaptMcpResult-CVlq-_TO.mjs";import{buildModelMessages as k}from"./packem_shared/buildModelMessages-Y4tGo1T9.mjs";import{agentBindingName as R,agentClassName as V,agentDefaultName as b,voiceBindingName as h,voiceClassName as w}from"./naming.mjs";import{AGENT_MODULE as I,DEFAULT_AGENT_FUNCTION_PATHS as P,SANDBOX_INVOKE_PATH as X,SANDBOX_MODULE as y,toFunctionReference as K}from"./packem_shared/AGENT_MODULE-CLaSQy3P.mjs";import{browserTool as j,containerTool as q,fsTool as z}from"./sandbox.mjs";import{defineSkill as Q,isSkillDefinition as Y}from"./packem_shared/defineSkill-DGGvDWNq.mjs";import{default as $}from"./packem_shared/VoiceSessionDO-A6u5NXI1.mjs";import{r as oe}from"./packem_shared/voice-turn-DXT1uMUn.mjs";import{default as re}from"./packem_shared/compileAgentWorkflow-BvkRUzx_.mjs";export{I as AGENT_MODULE,P as DEFAULT_AGENT_FUNCTION_PATHS,X as SANDBOX_INVOKE_PATH,y as SANDBOX_MODULE,$ as VoiceSessionDO,F as adaptMcpResult,l as agentAsTool,R as agentBindingName,V as agentClassName,c as agentComponent,b as agentDefaultName,x as agentExtension,j as browserTool,k as buildModelMessages,p as codeTool,a as collectAgenticMemoryTools,re as compileAgentWorkflow,q as containerTool,A as createAgentContext,M as createAgentGenerate,O as createEpisodeExtract,_ as createGraphExtract,C as createStreamGenerate,d as defineAgent,u as defineAgentTool,Q as defineSkill,z as fsTool,D as functionTool,N as isAgentDefinition,Y as isSkillDefinition,L as mcpTools,G as resolveAgentModel,t as runAgentLoop,oe as runVoiceTurn,r as splitForCompaction,K as toFunctionReference,i as toSearchResults,h as voiceBindingName,w as voiceClassName};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createAi as g}from"@lunora/ai";import{
|
|
1
|
+
import{createAi as g}from"@lunora/ai";import{H as b}from"./createDispatchRunner-CGafifJ7-C__lmjdK.mjs";import{t as m}from"./base64-5eyBfWO3.mjs";import{p as A,d as S,a as v,i as l,b as f,c as w,e as k,f as x,r as T,t as E}from"./voice-turn-DXT1uMUn.mjs";import{createStreamGenerate as M}from"./createAgentGenerate-9bZ2YhpN.mjs";import{DEFAULT_AGENT_FUNCTION_PATHS as B,toFunctionReference as p}from"./AGENT_MODULE-CLaSQy3P.mjs";const _="@cf/openai/whisper-large-v3-turbo",I="@cf/deepgram/aura-1",U=8*1024*1024,D=256*1024,y=15,F=5e3;class O{agent;ai;env;exportName;paths;streamGenerate;sttModel;ttsModel;audioBuffers=new Map;bufferedBytes=new Map;controllers=new Map;state;constructor(t,e,s,r){this.state=t,this.env=e,this.agent=s,this.exportName=r,this.paths=B,this.ai=g({binding:e.AI,env:e}),this.streamGenerate=M(s,e),this.sttModel=s.voice?.stt??_,this.ttsModel=s.voice?.tts??I}fetch(t){if(t.headers.get("Upgrade")!=="websocket")return new Response("Expected a WebSocket upgrade",{status:426});const s=new URL(t.url).searchParams.get("threadKey");if(!s)return new Response("Missing threadKey",{status:400});const r=globalThis.WebSocketPair,i=new r,n=i[0],o=i[1];this.state.acceptWebSocket(o);const c=crypto.randomUUID(),d=A(t.headers.get("x-lunora-identity")),a=S(t.headers.get("x-lunora-userid")),h=v(t.headers.get("x-lunora-identity-exp"));if(l(h))return f(o),new Response(null,{status:101,webSocket:n});o.serializeAttachment?.({connectionId:c,threadKey:s,turn:0,...h===void 0?{}:{expiresAt:h},...d===void 0?{}:{identity:d},...a===void 0?{}:{userId:a}}),this.send(o,{audioFormat:this.agent.voice?.audioFormat??"mp3",type:"ready"});const u=this.agent.voice?.greeting;return u&&u.length>0&&this.state.waitUntil?.(this.speakGreeting(o,c,s,a,d,u)),new Response(null,{status:101,webSocket:n})}async webSocketMessage(t,e){const s=t.deserializeAttachment?.();if(s){if(l(s.expiresAt)){f(t);return}try{if(typeof e=="string"){await this.handleControl(t,s,e);return}this.bufferAudio(s.connectionId,new Uint8Array(e),t)}catch(r){this.send(t,{message:r instanceof Error?r.message:String(r),type:"error"})}}}webSocketClose(t){this.cleanupSocket(t)}webSocketError(t){this.cleanupSocket(t)}resolveRun(t,e){const s=t===void 0&&e===void 0?void 0:{...e===void 0?{}:{claims:e},...t===void 0?{}:{userId:t}};return b({env:this.env,label:"@lunora/agent voice",...s===void 0?{}:{identity:s}})}async transcribe(t){const e=w(t);return k(await this.ai.run(this.sttModel,{audio:m(e)}))}async synthesize(t,e){const s=this.agent.voice?.speaker;return x(await this.ai.run(this.ttsModel,{text:t,...s===void 0?{}:{speaker:s}},e===void 0?void 0:{signal:e}))}async handleControl(t,e,s){let r;try{r=JSON.parse(s)}catch{return}if(r.type==="interrupt"){this.controllers.get(e.connectionId)?.abort();return}if(this.controllers.has(e.connectionId)){this.send(t,{message:"a turn is already in progress — send an interrupt before the next utterance",type:"error"});return}if(r.type==="commit"){const i=this.drainAudio(e.connectionId);await this.runTurn(t,e,{pcm:i});return}await this.runTurn(t,e,{text:r.text})}bufferAudio(t,e,s){const r=(this.bufferedBytes.get(t)??0)+e.byteLength;if(r>U){this.audioBuffers.delete(t),this.bufferedBytes.delete(t),this.send(s,{message:"utterance exceeded the maximum buffer — send a commit sooner",type:"error"});return}const i=this.audioBuffers.get(t)??[];i.push(e),this.audioBuffers.set(t,i),this.bufferedBytes.set(t,r)}drainAudio(t){const e=this.audioBuffers.get(t)??[];this.audioBuffers.delete(t),this.bufferedBytes.delete(t);const s=e.reduce((n,o)=>n+o.byteLength,0),r=new Uint8Array(s);let i=0;for(const n of e)r.set(n,i),i+=n.byteLength;return r}async runTurn(t,e,s){const r=new AbortController;this.controllers.set(e.connectionId,r);try{await T({agent:this.agent,connectionId:e.connectionId,env:this.env,exportName:this.exportName,paths:this.paths,run:this.resolveRun(e.userId,e.identity),send:i=>{this.send(t,i)},sendAudio:i=>{this.sendAudio(t,i)},signal:r.signal,streamGenerate:this.streamGenerate,synthesize:async(i,n)=>this.synthesizeWithSignal(i,n),threadKey:e.threadKey,transcribe:async i=>this.transcribe(i),turn:e.turn,waitForDrain:async()=>this.waitForSocketDrain(t),...e.userId===void 0?{}:{owner:e.userId},...s.pcm===void 0?{}:{pcm:s.pcm},...s.text===void 0?{}:{text:s.text}})}finally{this.controllers.get(e.connectionId)===r&&this.controllers.delete(e.connectionId),t.serializeAttachment?.({...e,turn:e.turn+1})}}async speakGreeting(t,e,s,r,i,n){const o=this.resolveRun(r,i),c=new AbortController,d=()=>c.signal.aborted;this.controllers.set(e,c);try{await o(p(this.paths.ensureThread),{agent:this.exportName,key:s,...this.agent.initialState===void 0?{}:{initialState:this.agent.initialState},...r===void 0?{}:{owner:r}});for await(const a of E(await this.synthesizeWithSignal(n,c.signal))){if(d()||(await this.waitForSocketDrain(t),d()))break;this.sendAudio(t,a)}d()||(await o(p(this.paths.appendMessage),{content:n,messageKey:"voice:greeting:assistant",role:"assistant",threadKey:s}),this.send(t,{text:n,type:"assistant_done"}))}catch(a){this.send(t,{message:a instanceof Error?a.message:String(a),type:"error"})}finally{this.controllers.get(e)===c&&this.controllers.delete(e)}}async synthesizeWithSignal(t,e){return e.aborted?new Uint8Array(0):this.synthesize(t,e)}cleanupSocket(t){const e=t.deserializeAttachment?.();e&&(this.controllers.get(e.connectionId)?.abort(),this.controllers.delete(e.connectionId),this.audioBuffers.delete(e.connectionId),this.bufferedBytes.delete(e.connectionId))}send(t,e){try{t.send(JSON.stringify(e))}catch{}}sendAudio(t,e){try{t.send(e)}catch{}}async waitForSocketDrain(t){const e=t;let s=0;for(;typeof e.bufferedAmount=="number"&&e.bufferedAmount>D&&s<F;)await new Promise(r=>{setTimeout(r,y)}),s+=y}}export{O as default};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{LunoraError as c}from"@lunora/errors";import{jsonSchema as i}from"ai";const s=t=>{if(t.structuredContent!==void 0)return t.structuredContent;const n=(t.content??[]).filter(e=>e.type==="text"&&typeof e.text=="string").map(e=>e.text).join(`
|
|
2
|
+
`);return t.isError===!0?`MCP tool error: ${n.length>0?n:"unknown error"}`:n.length>0?n:t.content},l=(t,n,e)=>({description:n.description??`MCP tool "${n.name}".`,execute:async r=>s(await t.callTool({arguments:r,name:n.name})),inputSchema:i(n.inputSchema),isLunoraAgentTool:!0,...e===void 0?{}:{needsApproval:typeof e=="function"?r=>e(n.name,r):e}}),u=async t=>{if(t.command!==void 0)throw new c("INTERNAL","@lunora/agent: mcpTools `command` (stdio transport) cannot run in the Workers runtime — pass a `url` for an HTTP/SSE MCP server, or inject a connected `client`");if(t.url===void 0)throw new c("INTERNAL","@lunora/agent: mcpTools requires a `url` (an HTTP/SSE MCP server) or an injected `client`");const n=new URL(t.url),{Client:e}=await import("@modelcontextprotocol/sdk/client/index.js");let r;if(t.transport==="sse"){const{SSEClientTransport:a}=await import("@modelcontextprotocol/sdk/client/sse.js");r=new a(n)}else{const{StreamableHTTPClientTransport:a}=await import("@modelcontextprotocol/sdk/client/streamableHttp.js");r=new a(n)}const o=new e({name:t.name??"lunora-agent",version:t.version??"0.0.0"});return await o.connect(r),o},T=async t=>{const n=t.client??await u(t),{tools:e}=await n.listTools(),r={};for(const o of e)t.only!==void 0&&!t.only.includes(o.name)||(r[`${t.prefix??""}${o.name}`]=l(n,o,t.needsApproval));return r};export{s as adaptMcpResult,T as mcpTools};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as a}from"@lunora/errors";import{jsonSchema as A}from"ai";const $=16,y=4e3,b=e=>{if(e===void 0)return e;const o=typeof e=="string"?e:JSON.stringify(e);return o.length<=y?e:`${o.slice(0,y)}… [truncated]`},T=(e,o)=>{let t=e;for(const s of o.split(".")){if(t===null||typeof t!="object"||!Object.hasOwn(t,s))return;t=t[s]}return t},c=(e,o)=>{if(Array.isArray(e))return e.map(r=>c(r,o));if(e===null||typeof e!="object")return e;const t=e;if(typeof t.$from=="string"){const r=t.$from;if(!(r in o))throw new a("BAD_REQUEST",`@lunora/agent: code step references unknown result "${r}" (define it in an earlier step)`);return typeof t.$path=="string"?T(o[r],t.$path):o[r]}const s={};for(const[r,i]of Object.entries(t))s[r]=c(i,o);return s},g=async(e,o,t,s)=>{const r=Array.isArray(e.steps)?e.steps.slice(0,s):[],i={},p=[],d=new Set;for(const n of r){if(d.has(n.id))throw new a("BAD_REQUEST",`@lunora/agent: duplicate code step id "${n.id}" — each step id must be unique (later steps reference an output by id via $from)`);d.add(n.id)}for(const n of r){const l=o[n.tool];if(!l)throw new a("BAD_REQUEST",`@lunora/agent: code step calls unknown tool "${n.tool}" (available: ${Object.keys(o).join(", ")})`);const m=c(n.input??{},i),f={...t,idempotencyKey:`${t.idempotencyKey}:${n.id}`,toolCallId:`${t.toolCallId}:${n.id}`},h=()=>Promise.resolve(l.execute(m,f)),u=await t.step.do(f.idempotencyKey,h);i[n.id]=u,p.push({id:n.id,output:b(u)})}return{final:p.at(-1)?.output,results:p}},v=e=>`Compose MULTIPLE tool calls in one turn as a script, instead of one call per turn. Provide \`steps\`: an ordered array of \`{ id, tool, input }\`. A later step's \`input\` may reference an earlier step's output with \`{ "$from": "<stepId>", "$path": "optional.dot.path" }\`. Available tools: ${Object.entries(e).map(([o,t])=>`"${o}" — ${t.description}`).join("; ")}.`,w=A({properties:{steps:{description:"Ordered tool calls; a later input may reference an earlier output via { $from, $path }.",items:{additionalProperties:!1,properties:{id:{description:"A name later steps reference this output by.",type:"string"},input:{additionalProperties:!0,description:"The tool input (may embed $from refs).",type:"object"},tool:{description:"The tool to call.",type:"string"}},required:["id","tool"],type:"object"},type:"array"}},required:["steps"],type:"object"}),S=(e,o={})=>{const t=e;if(!t||typeof t!="object"||Object.keys(t).length===0)throw new a("INTERNAL","@lunora/agent: codeTool requires a non-empty map of tools to compose");for(const[r,i]of Object.entries(t))if(i.needsApproval!==void 0&&i.needsApproval!==!1)throw new a("INTERNAL",`@lunora/agent: codeTool cannot compose "${r}" — it has a \`needsApproval\` gate a code-mode script can't pause for. Expose it as a normal top-level tool, or gate the whole script via codeTool's \`needsApproval\`.`);const s=o.maxSteps??$;return{description:o.description??v(e),execute:(r,i)=>g(r,e,i,s),inputSchema:w,isLunoraAgentTool:!0,...o.needsApproval===void 0?{}:{needsApproval:o.needsApproval}}};export{S as codeTool,c as resolveReferences,g as runToolScript};
|
package/dist/packem_shared/{compileAgentWorkflow-O_Kvw_Ze.mjs → compileAgentWorkflow-BvkRUzx_.mjs}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineWorkflow as s}from"@lunora/workflow";import{runAgentLoop as m}from"./runAgentLoop-
|
|
1
|
+
import{defineWorkflow as s}from"@lunora/workflow";import{runAgentLoop as m}from"./runAgentLoop-6fGTajbG.mjs";import{createStreamGenerate as p,createAgentGenerate as l,createEpisodeExtract as f,createGraphExtract as d,createCompact as u}from"./createAgentGenerate-9bZ2YhpN.mjs";import{agentDefaultName as c}from"../naming.mjs";import{DEFAULT_AGENT_FUNCTION_PATHS as A}from"./AGENT_MODULE-CLaSQy3P.mjs";import{H as T}from"./createDispatchRunner-CGafifJ7-C__lmjdK.mjs";import{otlpTelemetry as y}from"./otlpTelemetry-yAjOjxn0.mjs";const E=(r,t,a)=>t===void 0?r:T({env:a,identity:{userId:t},label:"@lunora/agent"}),O=(r,t,a)=>{const e=t.LUNORA_OTLP_ENDPOINT;if(typeof e!="string"||e===""||r.telemetry?.isEnabled===!1)return r;const n=typeof t.LUNORA_OTLP_TOKEN=="string"?t.LUNORA_OTLP_TOKEN:void 0,i=[r.telemetry?.integrations].flat().filter(o=>o!==void 0);return{...r,telemetry:{...r.telemetry,integrations:[...i,y({conversationId:a,endpoint:e,token:n})],isEnabled:!0}}},I=(r,t,a)=>s({handler:async e=>{const n=O(r,e.env,e.params.threadKey);return m({agent:n,compact:u(),env:e.env,exportName:t,extractGraph:d(),extractEpisode:f(),generate:l(n,e.env),instanceId:e.event.instanceId,params:e.params,paths:a?.paths??A,run:E(e.run,e.params.owner,e.env),step:e.step,streamGenerate:p(n,e.env)})},name:r.name??c(t)});export{I as default};
|
package/dist/packem_shared/{createAgentContext-HUKAccII.mjs → createAgentContext-CfD3hIZ2.mjs}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{H as g}from"./createDispatchRunner-CGafifJ7-C__lmjdK.mjs";import{LunoraError as o}from"@lunora/errors";import{h as l,B as p}from"./branch-marker-boZ00zmk.mjs";import{toFunctionReference as w,DEFAULT_AGENT_FUNCTION_PATHS as m}from"./AGENT_MODULE-CLaSQy3P.mjs";const T=(r,s,i)=>{const c={},u=w(m.patchThread),d=()=>i??g({env:r,label:"@lunora/agent"});for(const e of s){const a=()=>{const n=r[e.binding];if(!n||typeof n.create!="function"||typeof n.get!="function")throw new o("INTERNAL",`@lunora/agent: no Workflow binding "${e.binding}" on env for agent "${e.exportName}" — run codegen/dev so wrangler.jsonc declares it`);return n};c[e.exportName]={cancel:async n=>{await(await a().get(n)).terminate();try{await d()(u,{instanceId:n,status:"cancelled"})}catch{}},publicRun:e.publicRun===!0,run:async(n,t)=>{if(l(n))throw new o("BAD_REQUEST",`@lunora/agent: run input ${p}`);return{id:(await a().create({...t?.id===void 0?{}:{id:t.id},params:n})).id}},sendEvent:async(n,t)=>{await(await a().get(n)).sendEvent(t)},status:async n=>(await a().get(n)).status()}}return c};export{T as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as i}from"@lunora/errors";const O=t=>{let e="";for(let n=0;n<t.length;n+=32768)e+=String.fromCharCode(...t.subarray(n,n+32768));return btoa(e)},m=t=>O(t).replaceAll("+","-").replaceAll("/","_").replace(/=+$/,"");new TextDecoder;const b=new TextEncoder,g="=",A=t=>{for(let e=0;e<t.length;e+=1)if(t.charCodeAt(e)>255)return!1;return!0},R=t=>m(b.encode(JSON.stringify(t))),T=t=>!t.startsWith(g)&&A(t)?t:`${g}${m(b.encode(t))}`,_="/_lunora/scheduler/dispatch",L=3e4,S=t=>{let e=t.length;for(;e>0&&t[e-1]==="/";)e-=1;return t.slice(0,e)},v=Symbol("lunoraDispatchFailure"),E=Symbol("lunoraDispatchMessageId"),N=(t,e)=>(Object.defineProperty(t,v,{value:!0}),e!==void 0&&Object.defineProperty(t,E,{value:e}),t),x=(t,e,n,c)=>{try{const o=JSON.parse(n)?.error;if(typeof o=="object"&&o!==null&&typeof o.code=="string"){const{code:h,data:l,message:s}=o;return N(new i(h,typeof s=="string"?s:void 0,{data:l,status:e}),c)}}catch{}return N(new i("INTERNAL",`${t}: function dispatch failed (${String(e)}): ${n}`,{status:e}),c)},U=(t,e,n)=>new i("INTERNAL",`${t}: function dispatch to "${e}" timed out after ${String(n)}ms`,{status:503}),J=t=>{const{label:e}=t,n=globalThis.fetch,c=t.fetchImpl??(typeof n=="function"?n.bind(globalThis):void 0);return async(o,h,l={})=>{if(typeof c!="function")throw new TypeError(`${e}: no fetch implementation available — pass fetchImpl or run on a platform with global fetch`);const s=t.env.LUNORA_ORIGIN_URL;if(typeof s!="string"||s.length===0)throw new i("INTERNAL",`${e}: \`LUNORA_ORIGIN_URL\` must be set on the Worker env so a handler can call back into Lunora functions`);const f=t.env.LUNORA_ADMIN_TOKEN;if(typeof f!="string"||f.length===0)throw new i("INTERNAL",`${e}: \`LUNORA_ADMIN_TOKEN\` must be set on the Worker env to authenticate function dispatch`);const w=`${S(s)}${_}`,d={authorization:`Bearer ${f}`,"content-type":"application/json"};t.identity?.userId!==void 0&&(d["x-lunora-userid"]=T(t.identity.userId)),t.identity?.claims!==void 0&&(d["x-lunora-identity"]=R(t.identity.claims));const y=l.timeoutMs??L,$=AbortSignal.timeout(y),p=r=>{throw r instanceof Error&&r.name==="TimeoutError"?U(e,o.__lunoraRef,y):r};let a;try{a=await c(w,{body:JSON.stringify({args:h??{},functionPath:o.__lunoraRef,shardKey:l.shardKey}),headers:d,method:"POST",signal:$})}catch(r){return p(r)}if(!a.ok){let r;try{r=await a.text()}catch(I){return p(I)}throw x(e,a.status,r,l.messageId)}let u;try{u=await a.text()}catch(r){return p(r)}if(u.length!==0)try{return JSON.parse(u)}catch{throw new i("INTERNAL",`${e}: function dispatch returned a non-JSON body (${String(a.status)}): ${u}`,{status:a.status})}}};export{J as H};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as o}from"@lunora/errors";import{toFunctionReference as c}from"./AGENT_MODULE-CLaSQy3P.mjs";const m=(r,e)=>{if(typeof e.description!="string"||e.description.length===0)throw new o("INTERNAL","@lunora/agent: functionTool requires a non-empty `description` (the model decides from it)");if(e.inputSchema===void 0)throw new o("INTERNAL","@lunora/agent: functionTool requires an `inputSchema` (a zod schema or `jsonSchema(...)`)");const t=c(r);return{description:e.description,execute:(i,n)=>n.run(t,{...i,idempotencyKey:n.idempotencyKey}),inputSchema:e.inputSchema,isLunoraAgentTool:!0}};export{m as functionTool};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{d as z}from"./component-shared-Cn6E5d_q.mjs";import{resolveAgentModel as J}from"./createAgentGenerate-9bZ2YhpN.mjs";import{r as Q,f as W,m as
|
|
1
|
+
import{d as z}from"./component-shared-Cn6E5d_q.mjs";import{resolveAgentModel as J}from"./createAgentGenerate-9bZ2YhpN.mjs";import{r as Q,f as W,m as x,a as X}from"./memory-JI0lUu84.mjs";import{buildModelMessages as F}from"./buildModelMessages-Y4tGo1T9.mjs";import{agentBindingName as D}from"../naming.mjs";import{toFunctionReference as g}from"./AGENT_MODULE-CLaSQy3P.mjs";const V=8,G=e=>e===void 0?"null":JSON.stringify(e),Y=e=>e===void 0?[]:[e].flat(),Z=async(e,t)=>(await Promise.all(e.map(async o=>o({steps:t})))).some(Boolean),tt=(e,t)=>{if(t===void 0)return e;if(e===void 0)return{...t};const n={};for(const o of["inputTokens","outputTokens","totalTokens"])(e[o]!==void 0||t[o]!==void 0)&&(n[o]=(e[o]??0)+(t[o]??0));return n},et=async(e,t,n,o,a)=>{const{needsApproval:r}=e;return r===void 0||r===!1?!1:r===!0?!0:n.do(o,async()=>r(t,a))},ot=async(e,t)=>{const{instanceId:n,patchThread:o,persist:a,step:r}=e;await a({content:`Awaiting approval to run tool "${t.name}".`,messageKey:`${n}:approval:${t.id}`,role:"tool",status:"awaiting_approval",toolCallId:t.id,toolName:t.name}),await o({status:"awaiting_input"});const i=await r.waitForEvent(`approval:${t.id}`,{type:`agent-approval:${t.id}`});return await o({status:"running"}),{decision:i.payload.decision,...i.payload.note===void 0?{}:{note:i.payload.note}}},nt=async(e,t)=>{const{env:n,getState:o,instanceId:a,onTokenDelta:r,persist:i,run:d,setState:c,step:l,threadKey:s,tools:p}=e,u=`tool:${t.name}:${t.id}`,m=p[t.name],f=`${a}:tool:${t.id}`;if(!m){await i({content:`Error: unknown tool "${t.name}"`,messageKey:f,role:"tool",stepName:u,toolCallId:t.id,toolName:t.name});return}const S={env:n,getState:o,idempotencyKey:u,reportProgress:$=>{r?.({data:$,kind:"progress",threadKey:s,toolCallId:t.id})},run:d,setState:c,step:l,threadKey:s,toolCallId:t.id},v={env:n,getState:o,idempotencyKey:u,run:d,threadKey:s,toolCallId:t.id},C=`tool:approval-gate:${t.id}`;let h;if(await et(m,t.input,l,C,v)){const{decision:$,note:R}=await ot(e,t);if($==="reject"){const I=R===void 0?"":` Reason: ${R}`;await i({content:`Tool "${t.name}" was rejected by the user and not run.${I}`,messageKey:f,role:"tool",status:"rejected",stepName:u,toolCallId:t.id,toolName:t.name});return}h="approved"}const k=await l.do(u,()=>Promise.resolve(m.execute(t.input,S)));await i({content:typeof k=="string"?k:G(k),messageKey:f,role:"tool",stepName:u,...h===void 0?{}:{status:h},toolCallId:t.id,toolName:t.name})},at=(e,t,n)=>{if(!t)return e;let{messages:o}=e;return t.messages!==void 0&&(o=t.messages),t.system!==void 0&&(o=[{content:t.system,role:"system"},...o]),{activeTools:t.activeTools??e.activeTools,messages:o,model:t.model===void 0?e.model:J(t.model,n),toolChoice:t.toolChoice??e.toolChoice}},st=(e,t)=>{if(t===void 0||e.length<=t.maxMessages)return;const n=Math.max(1,t.keepRecent??Math.ceil(t.maxMessages/2));if(e.length<=n)return;let o=e.length-n;for(;o>0&&e[o]?.role==="tool";)o-=1;if(!(o<=0))return{older:e.slice(0,o),recent:e.slice(o)}},rt=async(e,t)=>{const{agent:n,compact:o,env:a}=e,r=st(t,n.compaction);if(r===void 0||o===void 0)return{history:t,summary:void 0};try{const i=await o({env:a,messages:F({history:r.older}),model:n.compaction?.model??n.model});return i.length>0?{history:r.recent,summary:i}:{history:t,summary:void 0}}catch{return{history:t,summary:void 0}}},it=async(e,t,n)=>{const{agent:o,env:a,generate:r,instructions:i,listMessages:d,memoryContext:c,onTokenDelta:l,run:s,step:p,streamGenerate:u,threadKey:m}=e;return p.do(`llm:turn:${String(t)}`,async()=>{const f=await s(d,{key:m}),{history:T,summary:S}=await rt(e,f);let v={activeTools:o.activeTools,messages:F({history:T,instructions:i,memoryContext:c,summary:S}),model:void 0,toolChoice:o.toolChoice};o.prepareStep&&(v=at(v,await o.prepareStep({messages:v.messages,stepNumber:t,steps:n}),a));const C={messages:v.messages,...v.activeTools===void 0?{}:{activeTools:v.activeTools},...v.model===void 0?{}:{model:v.model},...v.toolChoice===void 0?{}:{toolChoice:v.toolChoice}};return u&&l?u(C,h=>{l({text:h,threadKey:m,turn:t})}):r(C)})},q=async(e,t)=>{const{agent:n,step:o}=e;n.onStepFinish&&await o.do(`agent:step-finish:${String(t.turn)}`,async()=>(await n.onStepFinish?.(t),!0))},ct=e=>({text:e.text,toolCalls:e.toolCalls.map(t=>({input:t.input,toolCallId:t.id,toolName:t.name})),...e.usage===void 0?{}:{usage:e.usage}}),A=e=>{const t=e?.context;return typeof t=="string"&&t.length>0?t:void 0},ut=e=>z({depth:e?.depth,fanOut:e?.fanOut,maxNodes:e?.maxNodes,maxSeeds:e?.maxSeeds}),dt=async(e,t,n,o)=>{if(e.source===void 0)return;const a=g(e.source),{topK:r}=e,i=x("memory:retrieve",e.key),d=await n.do(i,async()=>o(a,{query:t,...r===void 0?{}:{topK:r}}));return A(d)},pt=async(e,t,n,o,a,r)=>{if(n===void 0)return;const i=x("memory:traverse",e.key),d=await a.do(i,async()=>r(o,{owner:n,query:t,...ut(e.graph)}));return A(d)},lt=async(e,t,n,o,a)=>{if(t===void 0)return;const r=x("memory:recall",e.key),i={owner:t,...e.episodic?.recall===void 0?{}:{limit:e.episodic.recall}},d=await o.do(r,async()=>a(n,i));return A(d)},mt=async(e,t)=>e.kind==="episodic"?lt(e,t.owner,t.episodeRecall,t.step,t.run):e.kind==="graph"?pt(e,t.input,t.owner,t.graphTraverse,t.step,t.run):dt(e,t.input,t.step,t.run),yt=e=>{const t=e.findIndex(n=>n.kind==="episodic");return e.filter((n,o)=>n.kind!=="episodic"||o===t)},vt=async(e,t,n,o,a,r)=>{const i=yt(Q(e));if(i.length===0)return;const d=g(o.graphTraverse),c=g(o.episodeRecall),l=[];for(const s of i){const p=await mt(s,{episodeRecall:c,graphTraverse:d,input:t,owner:n,run:r,step:a});p!==void 0&&l.push(p)}return l.length>0?l.join(`
|
|
2
2
|
|
|
3
|
-
`):void 0},gt=async e=>{const{agent:t,env:n,extractGraph:o,finalText:a,input:r,instanceId:i,owner:d,paths:c,run:l,step:s}=e;if(o===void 0||d===void 0||a===void 0||a.length===0)return;const p=W(t);if(p!==void 0)try{const u={assistantText:a,env:n,model:p.graph?.extractionModel??t.model,userInput:r},m=await s.do(
|
|
3
|
+
`):void 0},gt=async e=>{const{agent:t,env:n,extractGraph:o,finalText:a,input:r,instanceId:i,owner:d,paths:c,run:l,step:s}=e;if(o===void 0||d===void 0||a===void 0||a.length===0)return;const p=W(t);if(p!==void 0)try{const u={assistantText:a,env:n,model:p.graph?.extractionModel??t.model,userInput:r},m=await s.do(x("memory:extract",p.key),async()=>o(u));await l(g(c.graphUpsert),{...m,messageKey:`${i}:${x("extract",p.key)}`,owner:d})}catch{}},ft=async e=>{const{agent:t,env:n,extractEpisode:o,finalText:a,input:r,instanceId:i,owner:d,paths:c,run:l,step:s,threadKey:p}=e;if(o===void 0||d===void 0||a===void 0||a.length===0)return;const u=X(t);if(u!==void 0)try{const m={assistantText:a,env:n,model:u.episodic?.extractionModel??t.model,userInput:r},{summary:f}=await s.do(x("memory:episode",u.key),async()=>o(m));await l(g(c.episodeUpsert),{messageKey:`${i}:${x("episode",u.key)}`,owner:d,summary:f,threadKey:p})}catch{}},ht=async(e,t,n)=>{const o=e[D(t)];if(!(!o||typeof o.get!="function"))try{await(await o.get(n)).terminate()}catch{}},wt="12 hours",xt=async(e,t,n)=>{await e.waitForEvent(`dequeue:${n}`,{timeout:wt,type:`agent-dequeue:${t}:${n}`})},Tt=async(e,t,n)=>{const o=e.env[D(e.exportName)];if(!o||typeof o.get!="function")return;const a=o.get.bind(o);try{await e.step.do(`dequeue-wake:${n}`,async()=>(await(await a(n)).sendEvent({payload:{threadKey:t},type:`agent-dequeue:${t}:${n}`}),n))}catch{}},Ct=async(e,t,n)=>{const{onReply:o}=e,{replyRef:a}=t.params;if(!(o===void 0||a===void 0))try{await t.step.do("agent:reply",async()=>(await o({env:t.env,replyRef:a,result:n,threadKey:t.params.threadKey}),a.channel))}catch{}},St=async(e,t,n,o,a)=>{const{instanceId:r,persist:i}=e,d=n.usage===void 0?{}:{usage:n.usage};if(n.toolCalls.length===0){const c=n.output!==void 0&&n.text.length===0?G(n.output):n.text;return await i({content:c,messageKey:`${r}:assistant:${String(t)}`,role:"assistant"}),await q(e,{text:n.text,toolCalls:[],turn:t,...d}),{final:{stopped:"final",text:n.text,turns:t+1,...n.output===void 0?{}:{output:n.output}}}}await i({content:n.text,messageKey:`${r}:assistant:${String(t)}`,role:"assistant",toolCalls:n.toolCalls});for(const c of n.toolCalls)await nt(e,c);if(await q(e,{text:n.text,toolCalls:n.toolCalls,turn:t,...d}),o.push(ct(n)),a.length>0&&await Z(a,o))return"stopCondition"},kt=async(e,t,n,o)=>{const a=[];let r,i=!1,d=0;for(let c=0;c<t;c+=1){const l=await it(e,c,a);o.value=tt(o.value,l.usage),d=c+1;const s=await St(e,c,l,a,n);if(s==="stopCondition"){i=!0;break}if(s!==void 0){r=s.final;break}}return{final:r,stoppedByCondition:i,turnsRun:d}},Et=async e=>{const{agent:t,compact:n,env:o,exportName:a,extractEpisode:r,extractGraph:i,generate:d,instanceId:c,onTokenDelta:l,params:s,paths:p,run:u,step:m,streamGenerate:f}=e,T=t.maxTurns??V,S=Y(t.stopWhen),v=g(p.appendMessage),C=g(p.completeRun),h=g(p.ensureThread),k=g(p.listMessages),$=g(p.patchThread),R=g(p.setState),I=g(p.state),U=async y=>{await u(v,{threadKey:s.threadKey,...y})},P=async y=>{await u($,{key:s.threadKey,...y})},j=async()=>{await U({content:s.input,messageKey:`${c}:user`,role:"user"})},M=async y=>{const N=await u(C,{instanceId:c,key:s.threadKey,...y});N?.dequeued!==void 0&&await Tt({env:o,exportName:a,step:m},s.threadKey,N.dequeued)},B=async()=>await u(I,{key:s.threadKey}),O=async y=>{await u(R,{key:s.threadKey,state:y})},E=await u(h,{agent:a,instanceId:c,key:s.threadKey,...t.initialState===void 0?{}:{initialState:t.initialState},...t.onConcurrentRun===void 0?{}:{onConcurrentRun:t.onConcurrentRun},...s.owner===void 0?{}:{owner:s.owner},...s.title===void 0?{}:{title:s.title}});E?.outcome==="replaced"&&await ht(o,a,E.priorInstanceId);const _=await vt(t,s.input,s.owner,p,m,u),b=typeof t.instructions=="function"?t.instructions({env:o,input:s.input,threadKey:s.threadKey}):t.instructions,H={agent:t,compact:n,env:o,generate:d,getState:B,instanceId:c,instructions:b,listMessages:k,memoryContext:_,onTokenDelta:l,patchThread:P,persist:U,run:u,setState:O,step:m,streamGenerate:f,threadKey:s.threadKey,tools:t.tools??{}},K={value:void 0};try{E?.outcome==="queued"&&await xt(m,s.threadKey,c),await j();const{final:y,stoppedByCondition:N,turnsRun:L}=await kt(H,T,S,K),w=K.value===void 0?{}:{usage:K.value};return await gt({agent:t,env:o,extractGraph:i,finalText:y?.text,input:s.input,instanceId:c,owner:s.owner,paths:p,run:u,step:m}),await ft({agent:t,env:o,extractEpisode:r,finalText:y?.text,input:s.input,instanceId:c,owner:s.owner,paths:p,run:u,step:m,threadKey:s.threadKey}),y?(await Ct(t,{env:o,params:s,step:m},{...y,...w}),await M({status:"idle",...w}),{...y,...w}):N?(await M({status:"idle",...w}),{stopped:"stopCondition",turns:L,...w}):(await M({error:`agent stopped: maxTurns (${String(T)}) reached`,status:"error",...w}),{stopped:"maxTurns",turns:T,...w})}catch(y){throw await M({error:y instanceof Error?y.message:String(y),status:"error",...K.value===void 0?{}:{usage:K.value}}),y}};export{Et as runAgentLoop,st as splitForCompaction};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{LunoraError as c}from"@lunora/errors";import{initLunora as h}from"@lunora/server";import{v as t}from"@lunora/values";import{t as g}from"./base64-5eyBfWO3.mjs";const{internalAction:w}=h.dataModel().create(),m=()=>globalThis.document?.documentElement?.outerHTML??"",l=1e6,b=20,x=new TextEncoder,y=async(e,n)=>{const o=n.url??"";switch(n.op){case"content":return e.content(o);case"pdf":return{data:g(await e.pdf(o)),encoding:"base64",mediaType:"application/pdf"};case"scrape":return e.scrape(o,m);case"screenshot":{const s=await e.screenshot(o,{...n.fullPage===void 0?{}:{fullPage:n.fullPage},...n.type===void 0?{}:{type:n.type}});return{data:g(s),encoding:"base64",mediaType:n.type==="jpeg"?"image/jpeg":"image/png"}}default:throw new c("INTERNAL",`@lunora/agent: sandbox browser op "${n.op}" is not supported`)}},E=e=>{const n=[`exit code: ${String(e.code)}`];return e.stdout.length>0&&n.push(`stdout:
|
|
2
|
+
${e.stdout}`),e.stderr.length>0&&n.push(`stderr:
|
|
3
|
+
${e.stderr}`),n.join(`
|
|
4
|
+
|
|
5
|
+
`)},$=async(e,n)=>{const o=e.any();if(n.op==="exec")try{return E(await o.exec(n.command??"",{args:n.args??[]}))}catch(s){return`exec failed: ${s instanceof Error?s.message:String(s)}`}if(n.op==="fetch")return(await o.fetch(n.path??"/",{...n.body===void 0?{}:{body:n.body},method:n.method??"GET"})).text();throw new c("INTERNAL",`@lunora/agent: sandbox container op "${n.op}" is not supported`)},u=e=>{let n=0,o=e.length;for(;n<o&&e[n]==="/";)n+=1;for(;o>n&&e[o-1]==="/";)o-=1;return e.slice(n,o)},T=(e,n)=>{const o=u(e),s=[];for(const r of n.split("/"))if(!(r===""||r===".")){if(r===".."){if(s.length===0)throw new c("BAD_REQUEST","@lunora/agent: fs path escapes the sandbox root");s.pop();continue}s.push(r)}const a=s.join("/");return o&&a?`${o}/${a}`:o||a},S=async(e,n,o)=>{const s=n.length>0?`${n}/`:"",a=o.length>0?o.length+1:0,r=[];let i;for(let d=0;d<b;d+=1){const p=await e.list({prefix:s,...i===void 0?{}:{cursor:i}});for(const f of p.objects)r.push(f.key.slice(a));if(!p.truncated||p.cursor===void 0)return{entries:r,truncated:!1};i=p.cursor}return{entries:r,truncated:!0}},N=async(e,n,o)=>{const s=u(n),a=T(n,o.path??"");switch(o.op){case"ls":{const{entries:r,truncated:i}=await S(e,a,s);return i?{entries:r,truncated:!0}:{entries:r}}case"read":{const r=await e.head(a);if(r&&r.size>l)throw new c("BAD_REQUEST",`@lunora/agent: fs read: "${o.path??""}" is ${String(r.size)} bytes (max ${String(l)})`);const i=await e.get(a);if(!i)throw new c("NOT_FOUND",`@lunora/agent: fs read: no file at "${o.path??""}"`);return i.text()}case"rm":return await e.delete(a),{path:o.path??"",removed:!0};case"stat":{const r=await e.head(a);return r?{exists:!0,size:r.size}:{exists:!1}}case"write":{const r=o.content??"",i=x.encode(r).length;if(i>l)throw new c("BAD_REQUEST",`@lunora/agent: fs write: ${String(i)} bytes exceeds the max (${String(l)})`);return await e.put(a,r),{bytes:i,path:o.path??"",wrote:!0}}default:throw new c("INTERNAL",`@lunora/agent: sandbox fs op "${o.op}" is not supported`)}},_=()=>({invoke:w.input({args:t.optional(t.array(t.string())),body:t.optional(t.string()),bucket:t.optional(t.string()),command:t.optional(t.string()),content:t.optional(t.string()),fullPage:t.optional(t.boolean()),kind:t.union(t.literal("browser"),t.literal("container"),t.literal("fs")),method:t.optional(t.string()),name:t.optional(t.string()),op:t.string(),path:t.optional(t.string()),root:t.optional(t.string()),selector:t.optional(t.string()),type:t.optional(t.string()),url:t.optional(t.string())}).action(async({args:n,ctx:o})=>{const s=o,a=n;if(a.kind==="browser"){if(!s.browser)throw new c("INTERNAL","@lunora/agent: sandbox browser op needs `ctx.browser` — install @lunora/browser and run codegen");return y(s.browser,a)}if(a.kind==="fs"){const d=s.env?.[a.bucket??""];if(!d||typeof d.get!="function")throw new c("INTERNAL",`@lunora/agent: sandbox fs op found no R2 bucket "${a.bucket??""}" on env — declare the r2_bucket binding in wrangler.jsonc and run codegen`);return N(d,a.root??"",a)}const r=a.name??"",i=s.containers?.[r];if(!i)throw new c("INTERNAL",`@lunora/agent: sandbox container op found no ctx.containers["${r}"] — declare the container in lunora/containers.ts and run codegen`);return $(i,a)})});export{T as resolveFsKey,N as runFsOp,_ as sandboxComponent};
|
|
@@ -42,10 +42,16 @@ interface AgentStepLike {
|
|
|
42
42
|
* `idempotencyKey`.
|
|
43
43
|
*
|
|
44
44
|
* The `idempotencyKey` is the deterministic durable-step name
|
|
45
|
-
* (`tool:<name>:<toolCallId
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
45
|
+
* (`tool:<name>:<toolCallId>`, further suffixed per script step for a
|
|
46
|
+
* `codeTool`). A COMPLETED tool step is never re-run on a workflow replay
|
|
47
|
+
* (native step memoization) — but a step that FAILS mid-body is retried
|
|
48
|
+
* at-least-once, so a side-effecting tool (charge a card, send a mail) must
|
|
49
|
+
* dedupe on this key itself. `functionTool` forwards it to the dispatched
|
|
50
|
+
* function as `args.idempotencyKey` (pinned after the model input, so it
|
|
51
|
+
* can't be overridden) — a function that wants to dedupe declares
|
|
52
|
+
* `idempotencyKey: v.optional(v.string())` in its own args and checks it; a
|
|
53
|
+
* function that ignores it is unaffected (an undeclared arg field is dropped,
|
|
54
|
+
* not rejected).
|
|
49
55
|
* @experimental
|
|
50
56
|
*/
|
|
51
57
|
interface AgentToolContext {
|
|
@@ -102,22 +108,45 @@ interface AgentToolContext {
|
|
|
102
108
|
* skip the write when it is already present.
|
|
103
109
|
*/
|
|
104
110
|
setState: (state: Record<string, unknown>) => Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* The durable-step handle (`step.do`/`waitForEvent`). ALWAYS present:
|
|
113
|
+
* `agent-loop.ts` threads it into every tool's context unconditionally, and
|
|
114
|
+
* it is required rather than optional so a missing handle is a compile
|
|
115
|
+
* error instead of a silent durability downgrade. `codeTool` uses it to give
|
|
116
|
+
* each script step its OWN nested durable boundary — see `code-tool.ts` — so
|
|
117
|
+
* a failure at script step 3 retries only step 3, not steps 1–2's
|
|
118
|
+
* already-committed side effects. Cloudflare Workflows supports a `step.do`
|
|
119
|
+
* nested inside another `step.do`'s callback (the codeTool call's own
|
|
120
|
+
* enclosing step). Most tools never touch this directly; a test driving
|
|
121
|
+
* `execute`/`runToolScript` by hand passes a pass-through double.
|
|
122
|
+
*/
|
|
123
|
+
step: AgentStepLike;
|
|
105
124
|
/** The thread this tool call belongs to. */
|
|
106
125
|
threadKey: string;
|
|
107
126
|
/** The provider-issued tool-call id. */
|
|
108
127
|
toolCallId: string;
|
|
109
128
|
}
|
|
110
129
|
/**
|
|
111
|
-
* The
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
130
|
+
* The view of {@link AgentToolContext} handed to a `needsApproval` gate
|
|
131
|
+
* function — every field except `setState`, `step`, and `reportProgress`.
|
|
132
|
+
*
|
|
133
|
+
* `setState` is dropped because a gate that mutates thread state is a side
|
|
134
|
+
* effect inside a decision predicate, and `reportProgress` because emitting a
|
|
135
|
+
* live event is the same thing in observable form — the decision is what the
|
|
136
|
+
* loop reports, not the deciding. `step` goes because the loop already runs the
|
|
137
|
+
* gate inside a durable step of its own, so a gate has no business opening
|
|
138
|
+
* another.
|
|
139
|
+
*
|
|
140
|
+
* `getState` and `run` stay: reads are legitimate gate inputs (gate on the
|
|
141
|
+
* caller's plan tier, on a spend total), and the gate resolves inside its own
|
|
142
|
+
* durable step, so they are replay-safe there. Note this makes the type a
|
|
143
|
+
* NARROWING, not a proof of purity — `run` takes any
|
|
144
|
+
* {@link AgentFunctionReference}, so a gate can still dispatch a mutation
|
|
145
|
+
* before approval. Nothing in the type system distinguishes a query reference
|
|
146
|
+
* from a mutation one; keeping the gate side-effect-free is the author's.
|
|
118
147
|
* @experimental
|
|
119
148
|
*/
|
|
120
|
-
type AgentApprovalContext = Omit<AgentToolContext, "setState">;
|
|
149
|
+
type AgentApprovalContext = Omit<AgentToolContext, "reportProgress" | "setState" | "step">;
|
|
121
150
|
/**
|
|
122
151
|
* An agent tool. Unlike a raw AI SDK tool, `execute` is NOT handed to the
|
|
123
152
|
* model call — the loop runs it itself inside a named durable step so a
|
|
@@ -150,9 +179,11 @@ interface AgentToolDefinition<Input = unknown, Output = unknown> {
|
|
|
150
179
|
* from the tool's own step), so it now runs exactly once per call, not once
|
|
151
180
|
* per replay. It must still be otherwise pure: deterministic given its
|
|
152
181
|
* inputs (no `Date.now()`/`Math.random()`) and free of side effects — the
|
|
153
|
-
* context it receives is {@link AgentApprovalContext}, which has
|
|
154
|
-
* `setState`; state writes
|
|
155
|
-
* memoized step.
|
|
182
|
+
* context it receives is {@link AgentApprovalContext}, which has neither
|
|
183
|
+
* `setState` nor `reportProgress`; state writes and progress events belong
|
|
184
|
+
* only in `execute`, inside the tool's own memoized step. It still holds
|
|
185
|
+
* `run`, which the type cannot narrow to reads — see
|
|
186
|
+
* {@link AgentApprovalContext}.
|
|
156
187
|
*/
|
|
157
188
|
needsApproval?: ((input: Input, context: AgentApprovalContext) => boolean | Promise<boolean>) | boolean;
|
|
158
189
|
}
|
|
@@ -42,10 +42,16 @@ interface AgentStepLike {
|
|
|
42
42
|
* `idempotencyKey`.
|
|
43
43
|
*
|
|
44
44
|
* The `idempotencyKey` is the deterministic durable-step name
|
|
45
|
-
* (`tool:<name>:<toolCallId
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
45
|
+
* (`tool:<name>:<toolCallId>`, further suffixed per script step for a
|
|
46
|
+
* `codeTool`). A COMPLETED tool step is never re-run on a workflow replay
|
|
47
|
+
* (native step memoization) — but a step that FAILS mid-body is retried
|
|
48
|
+
* at-least-once, so a side-effecting tool (charge a card, send a mail) must
|
|
49
|
+
* dedupe on this key itself. `functionTool` forwards it to the dispatched
|
|
50
|
+
* function as `args.idempotencyKey` (pinned after the model input, so it
|
|
51
|
+
* can't be overridden) — a function that wants to dedupe declares
|
|
52
|
+
* `idempotencyKey: v.optional(v.string())` in its own args and checks it; a
|
|
53
|
+
* function that ignores it is unaffected (an undeclared arg field is dropped,
|
|
54
|
+
* not rejected).
|
|
49
55
|
* @experimental
|
|
50
56
|
*/
|
|
51
57
|
interface AgentToolContext {
|
|
@@ -102,22 +108,45 @@ interface AgentToolContext {
|
|
|
102
108
|
* skip the write when it is already present.
|
|
103
109
|
*/
|
|
104
110
|
setState: (state: Record<string, unknown>) => Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* The durable-step handle (`step.do`/`waitForEvent`). ALWAYS present:
|
|
113
|
+
* `agent-loop.ts` threads it into every tool's context unconditionally, and
|
|
114
|
+
* it is required rather than optional so a missing handle is a compile
|
|
115
|
+
* error instead of a silent durability downgrade. `codeTool` uses it to give
|
|
116
|
+
* each script step its OWN nested durable boundary — see `code-tool.ts` — so
|
|
117
|
+
* a failure at script step 3 retries only step 3, not steps 1–2's
|
|
118
|
+
* already-committed side effects. Cloudflare Workflows supports a `step.do`
|
|
119
|
+
* nested inside another `step.do`'s callback (the codeTool call's own
|
|
120
|
+
* enclosing step). Most tools never touch this directly; a test driving
|
|
121
|
+
* `execute`/`runToolScript` by hand passes a pass-through double.
|
|
122
|
+
*/
|
|
123
|
+
step: AgentStepLike;
|
|
105
124
|
/** The thread this tool call belongs to. */
|
|
106
125
|
threadKey: string;
|
|
107
126
|
/** The provider-issued tool-call id. */
|
|
108
127
|
toolCallId: string;
|
|
109
128
|
}
|
|
110
129
|
/**
|
|
111
|
-
* The
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
130
|
+
* The view of {@link AgentToolContext} handed to a `needsApproval` gate
|
|
131
|
+
* function — every field except `setState`, `step`, and `reportProgress`.
|
|
132
|
+
*
|
|
133
|
+
* `setState` is dropped because a gate that mutates thread state is a side
|
|
134
|
+
* effect inside a decision predicate, and `reportProgress` because emitting a
|
|
135
|
+
* live event is the same thing in observable form — the decision is what the
|
|
136
|
+
* loop reports, not the deciding. `step` goes because the loop already runs the
|
|
137
|
+
* gate inside a durable step of its own, so a gate has no business opening
|
|
138
|
+
* another.
|
|
139
|
+
*
|
|
140
|
+
* `getState` and `run` stay: reads are legitimate gate inputs (gate on the
|
|
141
|
+
* caller's plan tier, on a spend total), and the gate resolves inside its own
|
|
142
|
+
* durable step, so they are replay-safe there. Note this makes the type a
|
|
143
|
+
* NARROWING, not a proof of purity — `run` takes any
|
|
144
|
+
* {@link AgentFunctionReference}, so a gate can still dispatch a mutation
|
|
145
|
+
* before approval. Nothing in the type system distinguishes a query reference
|
|
146
|
+
* from a mutation one; keeping the gate side-effect-free is the author's.
|
|
118
147
|
* @experimental
|
|
119
148
|
*/
|
|
120
|
-
type AgentApprovalContext = Omit<AgentToolContext, "setState">;
|
|
149
|
+
type AgentApprovalContext = Omit<AgentToolContext, "reportProgress" | "setState" | "step">;
|
|
121
150
|
/**
|
|
122
151
|
* An agent tool. Unlike a raw AI SDK tool, `execute` is NOT handed to the
|
|
123
152
|
* model call — the loop runs it itself inside a named durable step so a
|
|
@@ -150,9 +179,11 @@ interface AgentToolDefinition<Input = unknown, Output = unknown> {
|
|
|
150
179
|
* from the tool's own step), so it now runs exactly once per call, not once
|
|
151
180
|
* per replay. It must still be otherwise pure: deterministic given its
|
|
152
181
|
* inputs (no `Date.now()`/`Math.random()`) and free of side effects — the
|
|
153
|
-
* context it receives is {@link AgentApprovalContext}, which has
|
|
154
|
-
* `setState`; state writes
|
|
155
|
-
* memoized step.
|
|
182
|
+
* context it receives is {@link AgentApprovalContext}, which has neither
|
|
183
|
+
* `setState` nor `reportProgress`; state writes and progress events belong
|
|
184
|
+
* only in `execute`, inside the tool's own memoized step. It still holds
|
|
185
|
+
* `run`, which the type cannot narrow to reads — see
|
|
186
|
+
* {@link AgentApprovalContext}.
|
|
156
187
|
*/
|
|
157
188
|
needsApproval?: ((input: Input, context: AgentApprovalContext) => boolean | Promise<boolean>) | boolean;
|
|
158
189
|
}
|
package/dist/reply.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Mailer } from '@lunora/mail';
|
|
2
2
|
import { InboundEmail } from '@lunora/mail/inbound';
|
|
3
|
-
import { a as AgentReplyRef } from "./packem_shared/types.d-
|
|
3
|
+
import { a as AgentReplyRef } from "./packem_shared/types.d-BoMo2eJj.mjs";
|
|
4
4
|
import 'ai';
|
|
5
5
|
/** The email arm of {@link AgentReplyRef}, narrowed for the helpers below. */
|
|
6
6
|
type EmailReplyRef = Extract<AgentReplyRef, {
|
package/dist/reply.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Mailer } from '@lunora/mail';
|
|
2
2
|
import { InboundEmail } from '@lunora/mail/inbound';
|
|
3
|
-
import { a as AgentReplyRef } from "./packem_shared/types.d-
|
|
3
|
+
import { a as AgentReplyRef } from "./packem_shared/types.d-BoMo2eJj.js";
|
|
4
4
|
import 'ai';
|
|
5
5
|
/** The email arm of {@link AgentReplyRef}, narrowed for the helpers below. */
|
|
6
6
|
type EmailReplyRef = Extract<AgentReplyRef, {
|
package/dist/sandbox.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b as AgentToolContext, c as AgentToolDefinition } from "./packem_shared/types.d-
|
|
1
|
+
import { b as AgentToolContext, c as AgentToolDefinition } from "./packem_shared/types.d-BoMo2eJj.mjs";
|
|
2
2
|
import '@lunora/mail/inbound';
|
|
3
3
|
import 'ai';
|
|
4
4
|
/**
|
|
@@ -25,8 +25,8 @@ type BrowserToolInput = {
|
|
|
25
25
|
/**
|
|
26
26
|
* The model-provided input to a {@link containerTool} call — a discriminated
|
|
27
27
|
* union on `op`. `fetch` sends an HTTP request to the container; `exec` asks it
|
|
28
|
-
* to run a command (
|
|
29
|
-
*
|
|
28
|
+
* to run a command (delegated to `ctx.containers.<name>.exec`, the first-class
|
|
29
|
+
* exec contract in `@lunora/container`; the container app serves its route).
|
|
30
30
|
* @experimental
|
|
31
31
|
*/
|
|
32
32
|
type ContainerToolInput = {
|
|
@@ -112,12 +112,13 @@ interface ContainerToolOptions {
|
|
|
112
112
|
description?: string;
|
|
113
113
|
/**
|
|
114
114
|
* Gate a call behind a human approval. Defaults to gating any command
|
|
115
|
-
* execution
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* Pass a boolean or your own
|
|
120
|
-
* replay-stable input, so keep it
|
|
115
|
+
* execution — an `exec` — plus any `fetch` using a non-idempotent method
|
|
116
|
+
* (POST/PUT/PATCH/DELETE), so a prompt-injected model cannot reach some
|
|
117
|
+
* other mutating route unattended. A read-only `fetch` runs unattended. The
|
|
118
|
+
* exec route itself needs no special case: `@lunora/container` reserves
|
|
119
|
+
* `/__lunora/*` and refuses a `fetch` into it. Pass a boolean or your own
|
|
120
|
+
* predicate to change that. Evaluated from replay-stable input, so keep it
|
|
121
|
+
* deterministic.
|
|
121
122
|
*/
|
|
122
123
|
needsApproval?: ((input: ContainerToolInput) => boolean) | boolean;
|
|
123
124
|
}
|
|
@@ -156,12 +157,12 @@ declare const browserTool: (options?: BrowserToolOptions) => AgentToolDefinition
|
|
|
156
157
|
*
|
|
157
158
|
* By default a read-only (GET/HEAD/OPTIONS, or method-omitted) `fetch` runs
|
|
158
159
|
* unattended while everything else is gated behind a human approval — an
|
|
159
|
-
* `exec`; a `fetch` whose path resolves to the privileged
|
|
160
|
+
* `exec`; a `fetch` whose path resolves to the privileged exec route (both
|
|
160
161
|
* reach the same command-execution path in the container, so gating on the
|
|
161
|
-
* `op` name alone would let a `fetch` to
|
|
162
|
+
* `op` name alone would let a `fetch` to it run a command unattended);
|
|
162
163
|
* and a `fetch` using a non-idempotent method (POST/PUT/PATCH/DELETE) to ANY
|
|
163
164
|
* route, since a prompt-injected model could otherwise mutate container state
|
|
164
|
-
* through some other privileged route just by avoiding the literal
|
|
165
|
+
* through some other privileged route just by avoiding the literal exec
|
|
165
166
|
* path. A `fetch` can still reach any *other read* route unattended, so scope
|
|
166
167
|
* the container's GET routes accordingly. Pass `opts.needsApproval` to widen
|
|
167
168
|
* or disable the gate.
|
package/dist/sandbox.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { b as AgentToolContext, c as AgentToolDefinition } from "./packem_shared/types.d-
|
|
1
|
+
import { b as AgentToolContext, c as AgentToolDefinition } from "./packem_shared/types.d-BoMo2eJj.js";
|
|
2
2
|
import '@lunora/mail/inbound';
|
|
3
3
|
import 'ai';
|
|
4
4
|
/**
|
|
@@ -25,8 +25,8 @@ type BrowserToolInput = {
|
|
|
25
25
|
/**
|
|
26
26
|
* The model-provided input to a {@link containerTool} call — a discriminated
|
|
27
27
|
* union on `op`. `fetch` sends an HTTP request to the container; `exec` asks it
|
|
28
|
-
* to run a command (
|
|
29
|
-
*
|
|
28
|
+
* to run a command (delegated to `ctx.containers.<name>.exec`, the first-class
|
|
29
|
+
* exec contract in `@lunora/container`; the container app serves its route).
|
|
30
30
|
* @experimental
|
|
31
31
|
*/
|
|
32
32
|
type ContainerToolInput = {
|
|
@@ -112,12 +112,13 @@ interface ContainerToolOptions {
|
|
|
112
112
|
description?: string;
|
|
113
113
|
/**
|
|
114
114
|
* Gate a call behind a human approval. Defaults to gating any command
|
|
115
|
-
* execution
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* Pass a boolean or your own
|
|
120
|
-
* replay-stable input, so keep it
|
|
115
|
+
* execution — an `exec` — plus any `fetch` using a non-idempotent method
|
|
116
|
+
* (POST/PUT/PATCH/DELETE), so a prompt-injected model cannot reach some
|
|
117
|
+
* other mutating route unattended. A read-only `fetch` runs unattended. The
|
|
118
|
+
* exec route itself needs no special case: `@lunora/container` reserves
|
|
119
|
+
* `/__lunora/*` and refuses a `fetch` into it. Pass a boolean or your own
|
|
120
|
+
* predicate to change that. Evaluated from replay-stable input, so keep it
|
|
121
|
+
* deterministic.
|
|
121
122
|
*/
|
|
122
123
|
needsApproval?: ((input: ContainerToolInput) => boolean) | boolean;
|
|
123
124
|
}
|
|
@@ -156,12 +157,12 @@ declare const browserTool: (options?: BrowserToolOptions) => AgentToolDefinition
|
|
|
156
157
|
*
|
|
157
158
|
* By default a read-only (GET/HEAD/OPTIONS, or method-omitted) `fetch` runs
|
|
158
159
|
* unattended while everything else is gated behind a human approval — an
|
|
159
|
-
* `exec`; a `fetch` whose path resolves to the privileged
|
|
160
|
+
* `exec`; a `fetch` whose path resolves to the privileged exec route (both
|
|
160
161
|
* reach the same command-execution path in the container, so gating on the
|
|
161
|
-
* `op` name alone would let a `fetch` to
|
|
162
|
+
* `op` name alone would let a `fetch` to it run a command unattended);
|
|
162
163
|
* and a `fetch` using a non-idempotent method (POST/PUT/PATCH/DELETE) to ANY
|
|
163
164
|
* route, since a prompt-injected model could otherwise mutate container state
|
|
164
|
-
* through some other privileged route just by avoiding the literal
|
|
165
|
+
* through some other privileged route just by avoiding the literal exec
|
|
165
166
|
* path. A `fetch` can still reach any *other read* route unattended, so scope
|
|
166
167
|
* the container's GET routes accordingly. Pass `opts.needsApproval` to widen
|
|
167
168
|
* or disable the gate.
|
package/dist/sandbox.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{LunoraError as a}from"@lunora/errors";import{jsonSchema as
|
|
1
|
+
import{LunoraError as a}from"@lunora/errors";import{jsonSchema as i}from"ai";import{toFunctionReference as p,SANDBOX_INVOKE_PATH as c}from"./packem_shared/AGENT_MODULE-CLaSQy3P.mjs";const s=p(c),d='Drive a headless browser: screenshot a page, render it to PDF, read its HTML content, or scrape it. Set `op` to "screenshot" | "pdf" | "content" | "scrape" and pass the target `url`.',h='Talk to a sandboxed container: `fetch` an HTTP path on it, or `exec` a command inside it. Set `op` to "fetch" (with `path`) or "exec" (with `command`).',l='Read and write files in a persistent sandbox filesystem. Set `op` to "ls" (list a directory), "read", "write" (with `content`), "rm", or "stat", and pass `path`.',u=e=>e.op==="write"||e.op==="rm",T=i({properties:{content:{description:"write: the file contents.",type:"string"},op:{description:"The filesystem operation to run.",enum:["ls","read","write","rm","stat"],type:"string"},path:{description:"The file or directory path, relative to the sandbox root.",type:"string"}},required:["op"],type:"object"}),m=i({properties:{fullPage:{description:"screenshot: capture the full scrollable page rather than just the viewport.",type:"boolean"},op:{description:"The browser operation to run.",enum:["screenshot","scrape","pdf","content"],type:"string"},selector:{description:"scrape: optional CSS selector hint to narrow the extraction.",type:"string"},type:{description:"screenshot: image encoding (default png).",enum:["png","jpeg"],type:"string"},url:{description:"The URL to operate on.",type:"string"}},required:["op","url"],type:"object"}),g=new Set(["DELETE","PATCH","POST","PUT"]),f=e=>e.op==="exec"||g.has((e.method??"GET").toUpperCase()),y=i({properties:{args:{description:"exec: command arguments.",items:{type:"string"},type:"array"},body:{description:"fetch: request body.",type:"string"},command:{description:"exec: the command to run.",type:"string"},method:{description:"fetch: HTTP method (default GET).",type:"string"},op:{description:"The container operation to run.",enum:["fetch","exec"],type:"string"},path:{description:"fetch: request path on the container (e.g. /health).",type:"string"}},required:["op"],type:"object"}),w=(e={})=>({description:e.description??d,execute:(t,r)=>r.run(s,{...t,kind:"browser"}),inputSchema:m,isLunoraAgentTool:!0,...e.needsApproval===void 0?{}:{needsApproval:e.needsApproval}}),O=(e,t={})=>{if(typeof e!="string"||e.length===0)throw new a("INTERNAL","@lunora/agent: containerTool requires a container `name` (the ctx.containers.<name> key from lunora/containers.ts)");const r=t.needsApproval??f;return{description:t.description??h,execute:(o,n)=>n.run(s,{...o,kind:"container",name:e}),inputSchema:y,isLunoraAgentTool:!0,needsApproval:r}},_=(e,t={})=>{if(typeof e!="string"||e.length===0)throw new a("INTERNAL","@lunora/agent: fsTool requires an R2 `bucket` binding name (the r2_bucket declared in wrangler.jsonc)");return{description:t.description??l,execute:(r,o)=>{const n=typeof t.root=="function"?t.root(o):t.root??"";return o.run(s,{...r,bucket:e,kind:"fs",root:n})},inputSchema:T,isLunoraAgentTool:!0,needsApproval:t.needsApproval??u}};export{w as browserTool,O as containerTool,_ as fsTool};
|
package/dist/skill-markdown.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/agent",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.59",
|
|
4
4
|
"description": "Durable AI agents for Lunora: defineAgent compiles a replay-safe tool-loop onto Cloudflare Workflows, with DO SQLite threads and live message subscriptions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -84,10 +84,10 @@
|
|
|
84
84
|
"access": "public"
|
|
85
85
|
},
|
|
86
86
|
"dependencies": {
|
|
87
|
-
"@lunora/ai": "1.0.0-alpha.
|
|
87
|
+
"@lunora/ai": "1.0.0-alpha.60",
|
|
88
88
|
"@lunora/errors": "1.0.0-alpha.22",
|
|
89
|
-
"@lunora/mail": "1.0.0-alpha.
|
|
90
|
-
"@lunora/server": "1.0.0-alpha.
|
|
89
|
+
"@lunora/mail": "1.0.0-alpha.51",
|
|
90
|
+
"@lunora/server": "1.0.0-alpha.78",
|
|
91
91
|
"@lunora/values": "1.0.0-alpha.27",
|
|
92
92
|
"@lunora/workflow": "1.0.0-alpha.30",
|
|
93
93
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{LunoraError as c}from"@lunora/errors";import{jsonSchema as i}from"ai";const s=t=>{if(t.structuredContent!==void 0)return t.structuredContent;const n=(t.content??[]).filter(e=>e.type==="text"&&typeof e.text=="string").map(e=>e.text).join(`
|
|
2
|
-
`);return t.isError===!0?`MCP tool error: ${n.length>0?n:"unknown error"}`:n.length>0?n:t.content},l=(t,n)=>({description:n.description??`MCP tool "${n.name}".`,execute:async e=>s(await t.callTool({arguments:e,name:n.name})),inputSchema:i(n.inputSchema),isLunoraAgentTool:!0}),u=async t=>{if(t.command!==void 0)throw new c("INTERNAL","@lunora/agent: mcpTools `command` (stdio transport) cannot run in the Workers runtime — pass a `url` for an HTTP/SSE MCP server, or inject a connected `client`");if(t.url===void 0)throw new c("INTERNAL","@lunora/agent: mcpTools requires a `url` (an HTTP/SSE MCP server) or an injected `client`");const n=new URL(t.url),{Client:e}=await import("@modelcontextprotocol/sdk/client/index.js");let r;if(t.transport==="sse"){const{SSEClientTransport:a}=await import("@modelcontextprotocol/sdk/client/sse.js");r=new a(n)}else{const{StreamableHTTPClientTransport:a}=await import("@modelcontextprotocol/sdk/client/streamableHttp.js");r=new a(n)}const o=new e({name:t.name??"lunora-agent",version:t.version??"0.0.0"});return await o.connect(r),o},p=async t=>{const n=t.client??await u(t),{tools:e}=await n.listTools(),r={};for(const o of e)t.only!==void 0&&!t.only.includes(o.name)||(r[`${t.prefix??""}${o.name}`]=l(n,o));return r};export{s as adaptMcpResult,p as mcpTools};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as a}from"@lunora/errors";import{jsonSchema as h}from"ai";const A=16,u=4e3,$=e=>{if(e===void 0)return e;const o=typeof e=="string"?e:JSON.stringify(e);return o.length<=u?e:`${o.slice(0,u)}… [truncated]`},b=(e,o)=>{let t=e;for(const s of o.split(".")){if(t===null||typeof t!="object"||!Object.hasOwn(t,s))return;t=t[s]}return t},c=(e,o)=>{if(Array.isArray(e))return e.map(r=>c(r,o));if(e===null||typeof e!="object")return e;const t=e;if(typeof t.$from=="string"){const r=t.$from;if(!(r in o))throw new a("BAD_REQUEST",`@lunora/agent: code step references unknown result "${r}" (define it in an earlier step)`);return typeof t.$path=="string"?b(o[r],t.$path):o[r]}const s={};for(const[r,i]of Object.entries(t))s[r]=c(i,o);return s},T=async(e,o,t,s)=>{const r=Array.isArray(e.steps)?e.steps.slice(0,s):[],i={},p=[],l=new Set;for(const n of r){if(l.has(n.id))throw new a("BAD_REQUEST",`@lunora/agent: duplicate code step id "${n.id}" — each step id must be unique (later steps reference an output by id via $from)`);l.add(n.id)}for(const n of r){const d=o[n.tool];if(!d)throw new a("BAD_REQUEST",`@lunora/agent: code step calls unknown tool "${n.tool}" (available: ${Object.keys(o).join(", ")})`);const y=c(n.input??{},i),m={...t,idempotencyKey:`${t.idempotencyKey}:${n.id}`,toolCallId:`${t.toolCallId}:${n.id}`},f=await d.execute(y,m);i[n.id]=f,p.push({id:n.id,output:$(f)})}return{final:p.at(-1)?.output,results:p}},g=e=>`Compose MULTIPLE tool calls in one turn as a script, instead of one call per turn. Provide \`steps\`: an ordered array of \`{ id, tool, input }\`. A later step's \`input\` may reference an earlier step's output with \`{ "$from": "<stepId>", "$path": "optional.dot.path" }\`. Available tools: ${Object.entries(e).map(([o,t])=>`"${o}" — ${t.description}`).join("; ")}.`,w=h({properties:{steps:{description:"Ordered tool calls; a later input may reference an earlier output via { $from, $path }.",items:{additionalProperties:!1,properties:{id:{description:"A name later steps reference this output by.",type:"string"},input:{additionalProperties:!0,description:"The tool input (may embed $from refs).",type:"object"},tool:{description:"The tool to call.",type:"string"}},required:["id","tool"],type:"object"},type:"array"}},required:["steps"],type:"object"}),E=(e,o={})=>{const t=e;if(!t||typeof t!="object"||Object.keys(t).length===0)throw new a("INTERNAL","@lunora/agent: codeTool requires a non-empty map of tools to compose");for(const[r,i]of Object.entries(t))if(i.needsApproval!==void 0&&i.needsApproval!==!1)throw new a("INTERNAL",`@lunora/agent: codeTool cannot compose "${r}" — it has a \`needsApproval\` gate a code-mode script can't pause for. Expose it as a normal top-level tool, or gate the whole script via codeTool's \`needsApproval\`.`);const s=o.maxSteps??A;return{description:o.description??g(e),execute:(r,i)=>T(r,e,i,s),inputSchema:w,isLunoraAgentTool:!0,...o.needsApproval===void 0?{}:{needsApproval:o.needsApproval}}};export{E as codeTool,c as resolveReferences,T as runToolScript};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as s}from"@lunora/errors";const I=t=>{let e="";for(let n=0;n<t.length;n+=32768)e+=String.fromCharCode(...t.subarray(n,n+32768));return btoa(e)},m=t=>I(t).replaceAll("+","-").replaceAll("/","_").replace(/=+$/,"");new TextDecoder;const w=new TextEncoder,N="=",O=t=>{for(let e=0;e<t.length;e+=1)if(t.charCodeAt(e)>255)return!1;return!0},R=t=>m(w.encode(JSON.stringify(t))),T=t=>!t.startsWith(N)&&O(t)?t:`${N}${m(w.encode(t))}`,L="/_lunora/scheduler/dispatch",_=3e4,E=t=>{let e=t.length;for(;e>0&&t[e-1]==="/";)e-=1;return t.slice(0,e)},S=Symbol("lunoraDispatchFailure"),g=t=>(Object.defineProperty(t,S,{value:!0}),t),v=(t,e,n)=>{try{const o=JSON.parse(n)?.error;if(typeof o=="object"&&o!==null&&typeof o.code=="string"){const{code:c,data:u,message:i}=o;return g(new s(c,typeof i=="string"?i:void 0,{data:u,status:e}))}}catch{}return g(new s("INTERNAL",`${t}: function dispatch failed (${String(e)}): ${n}`,{status:e}))},x=(t,e,n)=>new s("INTERNAL",`${t}: function dispatch to "${e}" timed out after ${String(n)}ms`,{status:503}),D=t=>{const{label:e}=t,n=globalThis.fetch,o=t.fetchImpl??(typeof n=="function"?n.bind(globalThis):void 0);return async(c,u,i={})=>{if(typeof o!="function")throw new TypeError(`${e}: no fetch implementation available — pass fetchImpl or run on a platform with global fetch`);const h=t.env.LUNORA_ORIGIN_URL;if(typeof h!="string"||h.length===0)throw new s("INTERNAL",`${e}: \`LUNORA_ORIGIN_URL\` must be set on the Worker env so a handler can call back into Lunora functions`);const f=t.env.LUNORA_ADMIN_TOKEN;if(typeof f!="string"||f.length===0)throw new s("INTERNAL",`${e}: \`LUNORA_ADMIN_TOKEN\` must be set on the Worker env to authenticate function dispatch`);const $=`${E(h)}${L}`,d={authorization:`Bearer ${f}`,"content-type":"application/json"};t.identity?.userId!==void 0&&(d["x-lunora-userid"]=T(t.identity.userId)),t.identity?.claims!==void 0&&(d["x-lunora-identity"]=R(t.identity.claims));const y=i.timeoutMs??_,b=AbortSignal.timeout(y),p=r=>{throw r instanceof Error&&r.name==="TimeoutError"?x(e,c.__lunoraRef,y):r};let a;try{a=await o($,{body:JSON.stringify({args:u??{},functionPath:c.__lunoraRef,shardKey:i.shardKey}),headers:d,method:"POST",signal:b})}catch(r){return p(r)}if(!a.ok){let r;try{r=await a.text()}catch(A){return p(A)}throw v(e,a.status,r)}let l;try{l=await a.text()}catch(r){return p(r)}if(l.length!==0)try{return JSON.parse(l)}catch{throw new s("INTERNAL",`${e}: function dispatch returned a non-JSON body (${String(a.status)}): ${l}`,{status:a.status})}}};export{D as v};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as n}from"@lunora/errors";import{toFunctionReference as c}from"./AGENT_MODULE-CLaSQy3P.mjs";const m=(o,e)=>{if(typeof e.description!="string"||e.description.length===0)throw new n("INTERNAL","@lunora/agent: functionTool requires a non-empty `description` (the model decides from it)");if(e.inputSchema===void 0)throw new n("INTERNAL","@lunora/agent: functionTool requires an `inputSchema` (a zod schema or `jsonSchema(...)`)");const r=c(o);return{description:e.description,execute:(t,i)=>i.run(r,t),inputSchema:e.inputSchema,isLunoraAgentTool:!0}};export{m as functionTool};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as c}from"@lunora/errors";import{initLunora as h}from"@lunora/server";import{v as t}from"@lunora/values";import{t as g}from"./base64-5eyBfWO3.mjs";const{internalAction:w}=h.dataModel().create(),m=()=>globalThis.document?.documentElement?.outerHTML??"",d=1e6,b=20,y=new TextEncoder,x=async(a,n)=>{const o=n.url??"";switch(n.op){case"content":return a.content(o);case"pdf":return{data:g(await a.pdf(o)),encoding:"base64",mediaType:"application/pdf"};case"scrape":return a.scrape(o,m);case"screenshot":{const s=await a.screenshot(o,{...n.fullPage===void 0?{}:{fullPage:n.fullPage},...n.type===void 0?{}:{type:n.type}});return{data:g(s),encoding:"base64",mediaType:n.type==="jpeg"?"image/jpeg":"image/png"}}default:throw new c("INTERNAL",`@lunora/agent: sandbox browser op "${n.op}" is not supported`)}},E=async(a,n)=>{const o=a.any();if(n.op==="exec")return(await o.fetch("/exec",{body:JSON.stringify({args:n.args??[],command:n.command??""}),headers:{"content-type":"application/json"},method:"POST"})).text();if(n.op==="fetch")return(await o.fetch(n.path??"/",{...n.body===void 0?{}:{body:n.body},method:n.method??"GET"})).text();throw new c("INTERNAL",`@lunora/agent: sandbox container op "${n.op}" is not supported`)},u=a=>{let n=0,o=a.length;for(;n<o&&a[n]==="/";)n+=1;for(;o>n&&a[o-1]==="/";)o-=1;return a.slice(n,o)},T=(a,n)=>{const o=u(a),s=[];for(const e of n.split("/"))if(!(e===""||e===".")){if(e===".."){if(s.length===0)throw new c("BAD_REQUEST","@lunora/agent: fs path escapes the sandbox root");s.pop();continue}s.push(e)}const r=s.join("/");return o&&r?`${o}/${r}`:o||r},N=async(a,n,o)=>{const s=n.length>0?`${n}/`:"",r=o.length>0?o.length+1:0,e=[];let i;for(let p=0;p<b;p+=1){const l=await a.list({prefix:s,...i===void 0?{}:{cursor:i}});for(const f of l.objects)e.push(f.key.slice(r));if(!l.truncated||l.cursor===void 0)return{entries:e,truncated:!1};i=l.cursor}return{entries:e,truncated:!0}},S=async(a,n,o)=>{const s=u(n),r=T(n,o.path??"");switch(o.op){case"ls":{const{entries:e,truncated:i}=await N(a,r,s);return i?{entries:e,truncated:!0}:{entries:e}}case"read":{const e=await a.head(r);if(e&&e.size>d)throw new c("BAD_REQUEST",`@lunora/agent: fs read: "${o.path??""}" is ${String(e.size)} bytes (max ${String(d)})`);const i=await a.get(r);if(!i)throw new c("NOT_FOUND",`@lunora/agent: fs read: no file at "${o.path??""}"`);return i.text()}case"rm":return await a.delete(r),{path:o.path??"",removed:!0};case"stat":{const e=await a.head(r);return e?{exists:!0,size:e.size}:{exists:!1}}case"write":{const e=o.content??"",i=y.encode(e).length;if(i>d)throw new c("BAD_REQUEST",`@lunora/agent: fs write: ${String(i)} bytes exceeds the max (${String(d)})`);return await a.put(r,e),{bytes:i,path:o.path??"",wrote:!0}}default:throw new c("INTERNAL",`@lunora/agent: sandbox fs op "${o.op}" is not supported`)}},R=()=>({invoke:w.input({args:t.optional(t.array(t.string())),body:t.optional(t.string()),bucket:t.optional(t.string()),command:t.optional(t.string()),content:t.optional(t.string()),fullPage:t.optional(t.boolean()),kind:t.union(t.literal("browser"),t.literal("container"),t.literal("fs")),method:t.optional(t.string()),name:t.optional(t.string()),op:t.string(),path:t.optional(t.string()),root:t.optional(t.string()),selector:t.optional(t.string()),type:t.optional(t.string()),url:t.optional(t.string())}).action(async({args:n,ctx:o})=>{const s=o,r=n;if(r.kind==="browser"){if(!s.browser)throw new c("INTERNAL","@lunora/agent: sandbox browser op needs `ctx.browser` — install @lunora/browser and run codegen");return x(s.browser,r)}if(r.kind==="fs"){const p=s.env?.[r.bucket??""];if(!p||typeof p.get!="function")throw new c("INTERNAL",`@lunora/agent: sandbox fs op found no R2 bucket "${r.bucket??""}" on env — declare the r2_bucket binding in wrangler.jsonc and run codegen`);return S(p,r.root??"",r)}const e=r.name??"",i=s.containers?.[e];if(!i)throw new c("INTERNAL",`@lunora/agent: sandbox container op found no ctx.containers["${e}"] — declare the container in lunora/containers.ts and run codegen`);return E(i,r)})});export{T as resolveFsKey,S as runFsOp,R as sandboxComponent};
|