@fungi.computer/mule 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
- Mule's direct runtime dependencies are Effect 3.22.1, Cairn, and
2
- @earendil-works/pi-ai 0.84.4. Consult each dependency's distribution for its
1
+ Mule's direct runtime dependencies are Effect 4.0.2, Cairn, and
2
+ @earendil-works/pi-ai 1.3.0. Consult each dependency's distribution for its
3
3
  license terms.
4
4
 
5
- The Pi dependency is pinned from commit
5
+ The original Pi loop source was pinned from commit
6
6
  853a80d26c90a14c1886f0ebb8ffaae133ca2185 and distributed under the MIT
7
7
  License, Copyright (c) 2025 Mario Zechner. Its independently archived source
8
8
  boundary has SHA-256
@@ -13,3 +13,8 @@ dfb11f4c1c00e47003db12765371198e944c8b640c995a1dc1cf9130bc9e49ee.
13
13
  behavior from Botanical's retained @shiit/agent-core 0.66.2 at commit
14
14
  b6eabe5dfd3cac9514025d26abbe9a92b083f10b, distributed under the MIT License,
15
15
  Copyright (c) 2024 fungi.computer.
16
+
17
+ `src/types.ts` adapts the provider-neutral data declarations from
18
+ @earendil-works/pi-ai 1.3.0, distributed under the MIT License,
19
+ Copyright (c) 2025 Mario Zechner. Mule owns this public structural contract;
20
+ provider implementations remain in Pi.
@@ -1,6 +1,6 @@
1
- import type { Api, AssistantMessage, AssistantMessageEvent, AssistantMessageEventStream, TranscriptContext, ImageContent, JsonValue, Message, Model, RetryPolicy, SimpleStreamOptions, TextContent, ToolCall, ToolResultMessage, Usage } from "@earendil-works/pi-ai";
1
+ import type { Api, AssistantMessage, AssistantMessageEvent, AssistantMessageEventStream, Context, ImageContent, JsonValue, Message, Model, RetryPolicy, SimpleStreamOptions, TextContent, ToolCall, ToolResultMessage, Usage } from "./types.js";
2
2
  /** Pi's model stream function, supplied explicitly by the caller. */
3
- export type MuleStreamFunction = (model: Model<Api>, context: TranscriptContext, options?: SimpleStreamOptions) => AssistantMessageEventStream | Promise<AssistantMessageEventStream>;
3
+ export type MuleStreamFunction = (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream | Promise<AssistantMessageEventStream>;
4
4
  /** Causal roles added around Pi's model and tool loop. */
5
5
  export type MuleIdentityKind = "run" | "turn" | "message" | "tool_call" | "tool_result" | "terminal";
6
6
  /** One run-local causal identity. */
package/dist/effect.d.ts CHANGED
@@ -1,9 +1,12 @@
1
+ export { decodeProviderData } from "./provider-data.js";
2
+ export { streamPi as streamModel } from "./provider.js";
1
3
  export { observeProviderStatus, type ProviderRejection, type ProviderStatusRecorder, } from "./provider-status.js";
2
- import type { AssistantMessage, Message, RetryPolicy, Tool, ToolCall } from "@earendil-works/pi-ai";
4
+ export { normalizeModel } from "./provider-model.js";
5
+ import type { AssistantMessage, Message, RetryPolicy, Tool, ToolCall } from "./types.js";
3
6
  import * as Effect from "effect/Effect";
4
7
  import * as Stream from "effect/Stream";
5
8
  import type { MuleCompletedTurn, MuleError, MuleEvent, MuleRetryObservation, MuleRunOutcome, MuleRunSettings, MuleToolExecutionPart, MuleToolRejection } from "./contract.js";
6
- export type { Api, AssistantMessage, AssistantMessageEvent, Context, ImageContent, JsonValue, Message, Model, RetryPolicy, SimpleStreamOptions, SystemMessage, TextContent, ThinkingContent, ToolCall, ToolResultMessage, Usage, UserMessage, } from "@earendil-works/pi-ai";
9
+ export type * from "./types.js";
7
10
  export { Type } from "typebox";
8
11
  export type { MuleRetryObservation } from "./contract.js";
9
12
  /**
package/dist/effect.js CHANGED
@@ -1,3 +1,6 @@
1
+ import { normalizeContext } from "@earendil-works/pi-ai/utils/transcript";
2
+ export { decodeProviderData } from "./provider-data.js";
3
+ export { streamPi as streamModel } from "./provider.js";
1
4
  import { nanoid } from "@fungi.computer/cairn";
2
5
  export { observeProviderStatus, } from "./provider-status.js";
3
6
  // Pi's root entry also evaluates its TypeBox validator compiler and model
@@ -5,7 +8,8 @@ export { observeProviderStatus, } from "./provider-status.js";
5
8
  // keeps that work out of every host's startup.
6
9
  import { isContextOverflow as piIsContextOverflow } from "@earendil-works/pi-ai/utils/overflow";
7
10
  import { DEFAULT_MAX_AGENT_RETRY_DELAY_MS, retryDelayMs, } from "@earendil-works/pi-ai/utils/retry";
8
- import { normalizeContext } from "@earendil-works/pi-ai/utils/transcript";
11
+ import { streamPi } from "./provider.js";
12
+ export { normalizeModel } from "./provider-model.js";
9
13
  import * as Effect from "effect/Effect";
10
14
  import * as Cause from "effect/Cause";
11
15
  import * as Result from "effect/Result";
@@ -271,8 +275,7 @@ function streamTurn(input, context, run, turn, emit, attempt) {
271
275
  piContext.tools = descriptors;
272
276
  const response = yield* withModelStreamDeadline(Effect.tryPromise({
273
277
  try: async () => {
274
- const stream = input.stream ??
275
- (await import("@earendil-works/pi-ai/compat")).streamSimple;
278
+ const stream = input.stream ?? streamPi;
276
279
  return stream(input.model, normalizeContext(piContext), options);
277
280
  },
278
281
  catch: (cause) => failure("model", "Pi model stream could not start", cause),
package/dist/index.d.ts CHANGED
@@ -1,4 +1,7 @@
1
- import type { Message, Tool, ToolCall } from "@earendil-works/pi-ai";
1
+ export { decodeProviderData } from "./provider-data.js";
2
+ export { streamPi as streamModel } from "./provider.js";
3
+ export { normalizeModel } from "./provider-model.js";
4
+ import type { Message, Tool, ToolCall } from "./types.js";
2
5
  export { observeProviderStatus, type ProviderRejection, type ProviderStatusRecorder, } from "./provider-status.js";
3
6
  import type { MuleCompletedTurn, MuleEvent, MuleRunOutcome, MuleRunSettings, MuleToolExecutionPart, MuleToolRejection } from "./contract.js";
4
7
  export type { MuleCompletedTurn, MuleResume, MuleToolCheckpoint, MuleError, MuleEvent, MuleIdentity, MuleIdentityKind, MuleRunOutcome, MuleRunSettings, MuleStreamFunction, MuleToolExecutionPart, MuleToolRejection, MuleToolResult, } from "./contract.js";
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ export { decodeProviderData } from "./provider-data.js";
2
+ export { streamPi as streamModel } from "./provider.js";
3
+ export { normalizeModel } from "./provider-model.js";
1
4
  export { observeProviderStatus, } from "./provider-status.js";
2
5
  import * as Cause from "effect/Cause";
3
6
  import * as Effect from "effect/Effect";
@@ -0,0 +1,6 @@
1
+ import type { SimpleStreamOptions as PiOptions } from "@earendil-works/pi-ai";
2
+ import type { SimpleStreamOptions } from "./types.js";
3
+ /** Adapt Pi's parsed payload and event boundary before invoking owned hooks. */
4
+ export declare function piOptions(options: SimpleStreamOptions | undefined): PiOptions | undefined;
5
+ /** Restore the owned options shape when Pi calls a scripted provider factory. */
6
+ export declare function muleOptions(options: PiOptions | undefined): SimpleStreamOptions | undefined;
@@ -0,0 +1,35 @@
1
+ import * as Schema from "effect/Schema";
2
+ import { decodeProviderData } from "./provider-data.js";
3
+ import { normalizeModel } from "./provider-model.js";
4
+ /** Adapt Pi's parsed payload and event boundary before invoking owned hooks. */
5
+ export function piOptions(options) {
6
+ if (options === undefined)
7
+ return undefined;
8
+ const { onPayload, onProviderStreamEvent, onResponse, ...rest } = options;
9
+ const adapted = { ...rest };
10
+ if (onPayload !== undefined)
11
+ adapted.onPayload = (payload, model) => onPayload(decodeProviderData(payload), normalizeModel(model));
12
+ if (onProviderStreamEvent !== undefined)
13
+ adapted.onProviderStreamEvent = (event, model) => onProviderStreamEvent(decodeProviderData(event), normalizeModel(model));
14
+ if (onResponse !== undefined)
15
+ adapted.onResponse = (response, model) => onResponse(response, normalizeModel(model));
16
+ return adapted;
17
+ }
18
+ /** Restore the owned options shape when Pi calls a scripted provider factory. */
19
+ export function muleOptions(options) {
20
+ if (options === undefined)
21
+ return undefined;
22
+ const { samplingParams, metadata, onPayload, ...rest } = options;
23
+ const adapted = { ...rest };
24
+ const decodeObject = Schema.decodeUnknownSync(Schema.Record(Schema.String, Schema.Json));
25
+ if (samplingParams !== undefined)
26
+ adapted.samplingParams = decodeObject(samplingParams);
27
+ if (metadata !== undefined)
28
+ adapted.metadata = decodeObject(metadata);
29
+ if (onPayload !== undefined)
30
+ adapted.onPayload = async (payload, model) => {
31
+ const result = await onPayload(payload, model);
32
+ return result === undefined ? undefined : decodeProviderData(result);
33
+ };
34
+ return adapted;
35
+ }
@@ -0,0 +1,3 @@
1
+ import type { ProviderData } from "./types.js";
2
+ /** Decode Pi's parsed request/event data before it enters an owned hook. */
3
+ export declare function decodeProviderData(data: unknown): ProviderData;
@@ -0,0 +1,17 @@
1
+ import * as Schema from "effect/Schema";
2
+ const providerValue = Schema.suspend(() => Schema.Union([
3
+ Schema.Undefined,
4
+ Schema.Null,
5
+ Schema.Boolean,
6
+ Schema.Number,
7
+ Schema.String,
8
+ Schema.Uint8Array,
9
+ Schema.instanceOf(AbortSignal),
10
+ Schema.Array(providerValue),
11
+ providerData,
12
+ ]));
13
+ const providerData = Schema.Record(Schema.String, providerValue);
14
+ /** Decode Pi's parsed request/event data before it enters an owned hook. */
15
+ export function decodeProviderData(data) {
16
+ return Schema.decodeUnknownSync(providerData)(data);
17
+ }
@@ -0,0 +1,3 @@
1
+ import type { Api, Model } from "./types.js";
2
+ /** Validate a catalog model's opaque sampling fields before it enters Mule. */
3
+ export declare function normalizeModel<TApi extends Api>(model: Omit<Model<TApi>, "samplingParams" | "samplingParamsByThinkingLevel">): Model<TApi>;
@@ -0,0 +1,18 @@
1
+ import * as Schema from "effect/Schema";
2
+ const sampling = Schema.Record(Schema.String, Schema.Json);
3
+ const decodeSampling = Schema.decodeUnknownSync(Schema.Struct({
4
+ samplingParams: Schema.optionalKey(sampling),
5
+ samplingParamsByThinkingLevel: Schema.optionalKey(Schema.Struct({
6
+ off: Schema.optionalKey(sampling),
7
+ minimal: Schema.optionalKey(sampling),
8
+ low: Schema.optionalKey(sampling),
9
+ medium: Schema.optionalKey(sampling),
10
+ high: Schema.optionalKey(sampling),
11
+ xhigh: Schema.optionalKey(sampling),
12
+ max: Schema.optionalKey(sampling),
13
+ })),
14
+ }));
15
+ /** Validate a catalog model's opaque sampling fields before it enters Mule. */
16
+ export function normalizeModel(model) {
17
+ return { ...model, ...decodeSampling(model) };
18
+ }
@@ -1,4 +1,4 @@
1
- import type { SimpleStreamOptions } from "@earendil-works/pi-ai";
1
+ import type { SimpleStreamOptions } from "./types.js";
2
2
  /** Bounded provider evidence, never response bodies, headers or error prose. */
3
3
  export type ProviderRejection = Readonly<{
4
4
  code: "usage-limit-reached";
@@ -0,0 +1,3 @@
1
+ import type { MuleStreamFunction } from "./contract.js";
2
+ /** Pi's maintained stream behind Mule's structural provider port. */
3
+ export declare const streamPi: MuleStreamFunction;
@@ -0,0 +1,7 @@
1
+ import { normalizeContext } from "@earendil-works/pi-ai/utils/transcript";
2
+ import { piOptions } from "./pi-options.js";
3
+ /** Pi's maintained stream behind Mule's structural provider port. */
4
+ export const streamPi = async (model, context, options) => {
5
+ const { streamSimple } = await import("@earendil-works/pi-ai/compat");
6
+ return streamSimple(model, normalizeContext(context), piOptions(options));
7
+ };
package/dist/testing.d.ts CHANGED
@@ -1,17 +1,60 @@
1
- import { type FauxProviderHandle, type FauxResponseStep, type RegisterFauxProviderOptions } from "@earendil-works/pi-ai/providers/faux";
2
- import type { Api, AssistantMessage, Model } from "@earendil-works/pi-ai";
1
+ import type { Api, AssistantMessage, AssistantMessageEventStream, Context, Model } from "./types.js";
2
+ import type { MuleStreamFunction } from "./contract.js";
3
+ /** Credential-free model definition for the scripted provider. */
4
+ export type FauxModelDefinition = {
5
+ id: string;
6
+ name?: string;
7
+ reasoning?: boolean;
8
+ input?: ("text" | "image")[];
9
+ inputLimits?: Model<Api>["inputLimits"];
10
+ cost?: {
11
+ input: number;
12
+ output: number;
13
+ cacheRead: number;
14
+ cacheWrite: number;
15
+ };
16
+ contextWindow?: number;
17
+ maxTokens?: number;
18
+ };
19
+ /** Scripted provider configuration, independent of provider SDK declarations. */
20
+ export type RegisterFauxProviderOptions = {
21
+ api?: string;
22
+ provider?: string;
23
+ models?: FauxModelDefinition[];
24
+ deferred?: {
25
+ pendingFetches?: number;
26
+ pollAfterMs?: number;
27
+ };
28
+ tokensPerSecond?: number;
29
+ tokenSize?: {
30
+ min?: number;
31
+ max?: number;
32
+ };
33
+ };
34
+ /** Counters retained by the scripted provider. */
35
+ export type FauxProviderState = {
36
+ callCount: number;
37
+ deferredFetchCount: number;
38
+ cancelledDeferred: NonNullable<AssistantMessage["deferred"]>[];
39
+ };
40
+ /** Function that observes a model request and supplies a scripted reply. */
41
+ export type FauxResponseFactory = (context: Context, options: Parameters<MuleStreamFunction>[2], state: FauxProviderState, model: Model<Api>) => AssistantMessage | Promise<AssistantMessage>;
42
+ /** A scripted reply or a factory that observes the normalized request. */
43
+ export type FauxResponseStep = AssistantMessage | FauxResponseFactory;
3
44
  /** Deterministic model and stream with Pi's observable response state. */
4
45
  export type FauxModel = Readonly<{
5
46
  /** Model supplied to Mule's run input. */
6
47
  model: Model<Api>;
7
48
  /** Bound Pi faux stream; never calls a remote provider. */
8
- stream: FauxProviderHandle["provider"]["streamSimple"];
49
+ stream: (...args: Parameters<MuleStreamFunction>) => AssistantMessageEventStream;
9
50
  /** Scripted replies have no typed provider failure; opt into retry explicitly. */
10
51
  retryable: (message: AssistantMessage) => boolean;
11
52
  /** Replace the responses consumed by subsequent assistant calls. */
12
53
  setResponses: (replies: readonly FauxResponseStep[]) => void;
13
54
  /** Pi's call history and counters for deterministic assertions. */
14
- state: FauxProviderHandle["state"];
55
+ state: FauxProviderState;
56
+ /** Scripted replies not yet consumed, supplied by Pi's maintained queue. */
57
+ getPendingResponseCount: () => number;
15
58
  }>;
16
59
  /** Create a credential-free model for tests and runnable examples. */
17
60
  export declare function fauxModel(replies: readonly FauxResponseStep[], options?: RegisterFauxProviderOptions): FauxModel;
package/dist/testing.js CHANGED
@@ -1,15 +1,23 @@
1
- import { fauxProvider, } from "@earendil-works/pi-ai/providers/faux";
1
+ import { isFunction } from "effect/Predicate";
2
+ import { muleOptions, piOptions } from "./pi-options.js";
3
+ import { normalizeModel } from "./provider-model.js";
4
+ import { fauxProvider } from "@earendil-works/pi-ai/providers/faux";
5
+ import { normalizeContext } from "@earendil-works/pi-ai/utils/transcript";
2
6
  /** Create a credential-free model for tests and runnable examples. */
3
7
  export function fauxModel(replies, options = {}) {
4
8
  const faux = fauxProvider(options);
5
- faux.setResponses([...replies]);
9
+ const setResponses = (responses) => {
10
+ faux.setResponses(responses.map((step) => isFunction(step)
11
+ ? (context, options, state, model) => step(context, muleOptions(options), state, normalizeModel(model))
12
+ : step));
13
+ };
14
+ setResponses(replies);
6
15
  return {
7
- model: faux.getModel(),
8
- stream: faux.provider.streamSimple.bind(faux.provider),
16
+ model: normalizeModel(faux.getModel()),
17
+ stream: (model, context, streamOptions) => faux.provider.streamSimple(model, normalizeContext(context), piOptions(streamOptions)),
9
18
  retryable: () => false,
10
- setResponses: (responses) => {
11
- faux.setResponses([...responses]);
12
- },
19
+ setResponses,
13
20
  state: faux.state,
21
+ getPendingResponseCount: () => faux.getPendingResponseCount(),
14
22
  };
15
23
  }