@jigs.design/ai-sdk 0.0.0-stage → 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tom Bates
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,45 @@
1
- # Temporary Holding Version
1
+ # @jigs.design/ai-sdk
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Optional. Connects Pickle to the [AI SDK](https://ai-sdk.dev) in two ways:
4
+
5
+ - `aiSdkModel(model, settings?)` is a `model` for `createPickleServer`. It passes the host's prompt as `instructions`, streams the text through `streamText`, and reports token usage to `onTurn`. It takes a gateway id such as `"anthropic/claude-sonnet-5.5"` or any provider's `LanguageModel`.
6
+ - `pickleChat(pickle, { request, messages })` runs chat mode on a `useChat` conversation. The assistant answers in Markdown, and each fenced `pickle` block becomes a `data-pickle` part you draw with `PickleView`.
7
+
8
+ ```ts
9
+ import { aiSdkModel } from "@jigs.design/ai-sdk";
10
+ import { createPickleServer } from "@jigs.design/server";
11
+
12
+ export const pickle = createPickleServer({
13
+ host,
14
+ context,
15
+ run,
16
+ model: aiSdkModel("anthropic/claude-sonnet-5.5", { temperature: 0.2 }),
17
+ });
18
+ ```
19
+
20
+ Import the root entry from server code. The browser imports `@jigs.design/ai-sdk/client`: `withoutPrograms`, `recentMessages` and the message types, with no server code and no Zod behind them.
21
+
22
+ ## Exports
23
+
24
+ | Export | What it is |
25
+ | -------------------------------------------- | -------------------------------------------------------------------------------- |
26
+ | `aiSdkModel`, `ModelSettings` | The model function and the `streamText` settings it applies to every call |
27
+ | `pickleChat` | One chat turn: the system prompt, the history converter and the stream transform |
28
+ | `pickleDataSchemas` | Schemas for `validateUIMessages`. They drop each pickle's `program` |
29
+ | `revivePickles` | Compiles a stored conversation's pickles again, typed from the messages passed |
30
+ | `withoutPrograms` | Strips programs from messages, for the request body and for storage |
31
+ | `recentMessages` | The recent turns of a long conversation, after every earlier pickle |
32
+ | `PickleUIMessage`, `PickleBlockData` | Types for `useChat` and for one `data-pickle` part, generic over your metadata |
33
+ | `StoredPickleUIMessage`, `StoredPickleBlock` | The same without programs: what the server receives and what you store |
34
+ | `@jigs.design/ai-sdk/client` | The browser entry: `withoutPrograms`, `recentMessages` and the types |
35
+
36
+ ## Source
37
+
38
+ - `src/model.ts`: `aiSdkModel`.
39
+ - `src/chat.ts`: chat mode on `useChat`.
40
+ - `src/stored.ts`, `src/client.ts`: the message types, `withoutPrograms`, `recentMessages`, and the browser entry. No Zod here.
41
+ - `src/schemas.ts`: `pickleDataSchemas`, server only.
42
+
43
+ ## Docs
44
+
45
+ [AI SDK](../../apps/docs/docs/running/ai-sdk.mdx) and [Chat](../../apps/docs/docs/running/chat.mdx). [`examples/with-ai-chat`](../../examples/with-ai-chat) runs both.
package/dist/chat.d.ts ADDED
@@ -0,0 +1,58 @@
1
+ import { PickleBlockData, PickleUIMessage, WithPickleBlocks } from "./stored.js";
2
+ import { ChatTurn, PickleServer } from "@jigs.design/server";
3
+ import { InferUIMessageChunk, UIMessage, UIMessageChunk } from "ai";
4
+ //#region src/chat.d.ts
5
+ export interface PickleChat {
6
+ /**
7
+ * For `convertToModelMessages(messages, { convertDataPart })`: writes each
8
+ * pickle back as the fenced block the model wrote, so it sees its UIs.
9
+ * Returns `undefined` for other data parts; chain your own after it.
10
+ */
11
+ convertDataPart: (part: {
12
+ data: unknown;
13
+ type: string;
14
+ }) => {
15
+ text: string;
16
+ type: "text";
17
+ } | undefined;
18
+ /** The chat-mode prompt for the server's host. Add your instructions to it. */
19
+ system: string;
20
+ /**
21
+ * The start of `system` that is the same on every turn. Pass both to
22
+ * `cachedInstructions` so the provider caches it.
23
+ */
24
+ systemPrefix: string;
25
+ /**
26
+ * Turns a UI message stream, e.g. `result.toUIMessageStream()`, into one
27
+ * where pickles are `data-pickle` parts. Prose stays in text parts, in the
28
+ * reply's order; a block's part updates in place as it streams and when it
29
+ * is repaired, and the stream finishes after every repair. Cancelling it
30
+ * stops the repairs. `TMessage` is the app's message type, for
31
+ * `createUIMessageStream`; its `pickle` data part must be a
32
+ * `PickleBlockData` or a `StoredPickleBlock`.
33
+ */
34
+ toUIMessageStream: <TMessage extends UIMessage = PickleUIMessage>(stream: ReadableStream<UIMessageChunk>) => ReadableStream<InferUIMessageChunk<TMessage>>;
35
+ /** The chat turn under it, for anything the stream transform does not cover. */
36
+ turn: ChatTurn;
37
+ }
38
+ /**
39
+ * Starts one chat reply on the AI SDK. Runs the server's `context` for
40
+ * `request` (a PickleError it throws rejects, as on a call) and reads the
41
+ * conversation's earlier pickles from `messages`, so edits and versions
42
+ * continue from them.
43
+ */
44
+ export declare function pickleChat<TContext, TSpecName extends string>(pickle: PickleServer<TContext, TSpecName>, input: {
45
+ messages: ReadonlyArray<UIMessage>;
46
+ /** App options for the server's `options` schema, passed to repairs. */
47
+ options?: unknown;
48
+ request: Request;
49
+ }): Promise<PickleChat>;
50
+ /**
51
+ * Compiles the pickles of a stored conversation again, for showing it after
52
+ * a reload. Programs are rebuilt from each block's `source` here, never read
53
+ * back from storage. Other parts and the metadata pass through as they are,
54
+ * so a `StoredPickleUIMessage<Metadata>`, or the stored form of your own
55
+ * message type, comes back with `PickleBlockData` in its `data-pickle` parts.
56
+ */
57
+ export declare function revivePickles<TMessage extends UIMessage>(pickle: Pick<PickleServer<unknown>, "compile">, messages: ReadonlyArray<TMessage>): Promise<Array<WithPickleBlocks<TMessage, PickleBlockData>>>;
58
+ //#endregion
package/dist/chat.js ADDED
@@ -0,0 +1,223 @@
1
+ import { storedBlock } from "./schemas.js";
2
+ import { chatBlockText } from "@jigs.design/server";
3
+ import { z } from "zod";
4
+ //#region src/chat.ts
5
+ const blockSource = z.object({
6
+ id: z.string(),
7
+ source: z.string()
8
+ });
9
+ /** The conversation's blocks, oldest first, read from its `data-pickle` parts. */
10
+ function blocksIn(messages) {
11
+ const blocks = [];
12
+ for (const message of messages) {
13
+ if (message.role !== "assistant") continue;
14
+ for (const part of message.parts) {
15
+ if (part.type !== "data-pickle" || !("data" in part)) continue;
16
+ const parsed = blockSource.safeParse(part.data);
17
+ if (parsed.success) blocks.push(parsed.data);
18
+ }
19
+ }
20
+ return blocks;
21
+ }
22
+ /** The text of the last user message: what this reply answers, for repairs. */
23
+ function lastPrompt(messages) {
24
+ const text = messages.findLast((entry) => entry.role === "user")?.parts.flatMap((part) => part.type === "text" ? [part.text] : []).join("\n");
25
+ return text === void 0 || text === "" ? void 0 : text;
26
+ }
27
+ /**
28
+ * Starts one chat reply on the AI SDK. Runs the server's `context` for
29
+ * `request` (a PickleError it throws rejects, as on a call) and reads the
30
+ * conversation's earlier pickles from `messages`, so edits and versions
31
+ * continue from them.
32
+ */
33
+ async function pickleChat(pickle, input) {
34
+ const stop = new AbortController();
35
+ const turn = await pickle.chat(input.request, {
36
+ blocks: blocksIn(input.messages),
37
+ options: input.options,
38
+ prompt: lastPrompt(input.messages),
39
+ signal: AbortSignal.any([input.request.signal, stop.signal])
40
+ });
41
+ return {
42
+ convertDataPart: (part) => {
43
+ if (part.type !== "data-pickle") return;
44
+ const parsed = blockSource.safeParse(part.data);
45
+ return parsed.success ? {
46
+ text: chatBlockText(parsed.data),
47
+ type: "text"
48
+ } : void 0;
49
+ },
50
+ system: turn.system,
51
+ systemPrefix: turn.systemPrefix,
52
+ toUIMessageStream: (stream) => transform(turn, stream, stop),
53
+ turn
54
+ };
55
+ }
56
+ /** What a block that was still open says when its reply stops. */
57
+ const STOPPED = {
58
+ code: "INTERNAL",
59
+ message: "The reply stopped before this pickle was finished."
60
+ };
61
+ function transform(turn, stream, stop) {
62
+ const reader = stream.getReader();
63
+ let closed = false;
64
+ return new ReadableStream({
65
+ async cancel(reason) {
66
+ closed = true;
67
+ stop.abort(reason);
68
+ await reader.cancel(reason);
69
+ },
70
+ async start(controller) {
71
+ const repairs = /* @__PURE__ */ new Set();
72
+ let open = null;
73
+ let replying = null;
74
+ let segment = 0;
75
+ const send = (chunk) => {
76
+ if (!closed) controller.enqueue(chunk);
77
+ };
78
+ const closeText = () => {
79
+ if (open !== null) {
80
+ send({
81
+ id: open,
82
+ type: "text-end"
83
+ });
84
+ open = null;
85
+ }
86
+ };
87
+ const sendBlock = (block) => {
88
+ send({
89
+ data: block,
90
+ id: block.key,
91
+ type: "data-pickle"
92
+ });
93
+ };
94
+ const repair = (block) => {
95
+ const task = (async () => {
96
+ try {
97
+ for await (const fixed of turn.repair(block)) sendBlock(fixed);
98
+ } catch {
99
+ sendBlock({
100
+ ...block,
101
+ error: {
102
+ code: "INTERNAL",
103
+ message: "The repair failed."
104
+ },
105
+ status: "done"
106
+ });
107
+ }
108
+ })();
109
+ repairs.add(task);
110
+ task.finally(() => repairs.delete(task));
111
+ };
112
+ const handle = (events, base, stopped = false) => {
113
+ for (const event of events) if (event.type === "text") {
114
+ if (event.text === "") continue;
115
+ if (open === null) {
116
+ open = `${base}-${segment}`;
117
+ segment++;
118
+ send({
119
+ id: open,
120
+ type: "text-start"
121
+ });
122
+ }
123
+ send({
124
+ delta: event.text,
125
+ id: open,
126
+ type: "text-delta"
127
+ });
128
+ } else if (stopped) {
129
+ closeText();
130
+ sendBlock({
131
+ ...event.block,
132
+ error: STOPPED,
133
+ status: "done"
134
+ });
135
+ } else {
136
+ closeText();
137
+ sendBlock(event.block);
138
+ if (event.block.status === "repairing") repair(event.block);
139
+ }
140
+ };
141
+ const cutShort = () => {
142
+ if (replying !== null) {
143
+ handle(turn.end(), replying, true);
144
+ replying = null;
145
+ }
146
+ closeText();
147
+ };
148
+ try {
149
+ for (;;) {
150
+ const step = await reader.read();
151
+ if (step.done) break;
152
+ const chunk = step.value;
153
+ switch (chunk.type) {
154
+ case "text-start":
155
+ replying = chunk.id;
156
+ break;
157
+ case "text-delta":
158
+ handle(turn.push(chunk.delta), chunk.id);
159
+ break;
160
+ case "text-end":
161
+ replying = null;
162
+ handle(turn.end(), chunk.id);
163
+ closeText();
164
+ break;
165
+ case "abort":
166
+ case "error":
167
+ cutShort();
168
+ send(chunk);
169
+ break;
170
+ case "finish":
171
+ closeText();
172
+ await Promise.all(repairs);
173
+ send(chunk);
174
+ break;
175
+ default: send(chunk);
176
+ }
177
+ }
178
+ cutShort();
179
+ await Promise.all(repairs);
180
+ if (!closed) {
181
+ closed = true;
182
+ controller.close();
183
+ }
184
+ } catch (error) {
185
+ if (!closed) {
186
+ cutShort();
187
+ closed = true;
188
+ controller.error(error);
189
+ }
190
+ stop.abort(error);
191
+ }
192
+ }
193
+ });
194
+ }
195
+ /**
196
+ * Compiles the pickles of a stored conversation again, for showing it after
197
+ * a reload. Programs are rebuilt from each block's `source` here, never read
198
+ * back from storage. Other parts and the metadata pass through as they are,
199
+ * so a `StoredPickleUIMessage<Metadata>`, or the stored form of your own
200
+ * message type, comes back with `PickleBlockData` in its `data-pickle` parts.
201
+ */
202
+ async function revivePickles(pickle, messages) {
203
+ return await Promise.all(messages.map(async (message) => ({
204
+ ...message,
205
+ parts: await Promise.all(message.parts.map(async (part) => {
206
+ if (part.type !== "data-pickle" || !("data" in part)) return part;
207
+ const stored = storedBlock.parse(part.data);
208
+ const compiled = await pickle.compile(stored.source, { strict: false });
209
+ const data = {
210
+ ...stored,
211
+ diagnostics: compiled.diagnostics,
212
+ program: compiled.program,
213
+ status: "done"
214
+ };
215
+ return {
216
+ ...part,
217
+ data
218
+ };
219
+ }))
220
+ })));
221
+ }
222
+ //#endregion
223
+ export { pickleChat, revivePickles };
@@ -0,0 +1,2 @@
1
+ import { PickleBlockData, PickleDataTypes, PickleUIMessage, StoredPickleBlock, StoredPickleUIMessage, recentMessages, withoutPrograms } from "./stored.js";
2
+ export { type PickleBlockData, type PickleDataTypes, type PickleUIMessage, type StoredPickleBlock, type StoredPickleUIMessage, recentMessages, withoutPrograms };
package/dist/client.js ADDED
@@ -0,0 +1 @@
1
+ export { recentMessages, withoutPrograms } from "./stored.js";
@@ -0,0 +1,5 @@
1
+ import { PickleBlockData, PickleDataTypes, PickleUIMessage, StoredPickleBlock, StoredPickleUIMessage, recentMessages, withoutPrograms } from "./stored.js";
2
+ import { PickleChat, pickleChat, revivePickles } from "./chat.js";
3
+ import { ModelSettings, aiSdkModel, cachedInstructions } from "./model.js";
4
+ import { pickleDataSchemas } from "./schemas.js";
5
+ export { type ModelSettings, type PickleBlockData, type PickleChat, type PickleDataTypes, type PickleUIMessage, type StoredPickleBlock, type StoredPickleUIMessage, aiSdkModel, cachedInstructions, pickleChat, pickleDataSchemas, recentMessages, revivePickles, withoutPrograms };
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import { pickleDataSchemas } from "./schemas.js";
2
+ import { pickleChat, revivePickles } from "./chat.js";
3
+ import { aiSdkModel, cachedInstructions } from "./model.js";
4
+ import { recentMessages, withoutPrograms } from "./stored.js";
5
+ export { aiSdkModel, cachedInstructions, pickleChat, pickleDataSchemas, recentMessages, revivePickles, withoutPrograms };
@@ -0,0 +1,21 @@
1
+ import { ModelFunction } from "@jigs.design/server";
2
+ import { Instructions, LanguageModel, streamText } from "ai";
3
+ //#region src/model.d.ts
4
+ /** `streamText` settings that apply to every call, such as `temperature`. */
5
+ export type ModelSettings = Omit<Parameters<typeof streamText>[0], "abortSignal" | "instructions" | "messages" | "model" | "prompt" | "system">;
6
+ /**
7
+ * `system` as instructions whose first part, `prefix`, a provider may cache:
8
+ * two system messages, the first marked as a cache breakpoint for providers
9
+ * that need one (Anthropic). Providers that cache a repeated prefix on their
10
+ * own (OpenAI, DeepSeek, Google) read the two as one. Without a prefix that
11
+ * `system` starts with, `system` alone.
12
+ */
13
+ export declare function cachedInstructions(system: string, prefix: string): Instructions;
14
+ /**
15
+ * A `model` for `createPickleServer` from an AI SDK model: document-mode
16
+ * sessions and chat repairs stream through `streamText`, and token usage
17
+ * reaches `onTurn`. The prompt's stable start is marked for the provider's
18
+ * cache (`cachedInstructions`).
19
+ */
20
+ export declare function aiSdkModel(model: LanguageModel, settings?: ModelSettings): ModelFunction<unknown, unknown>;
21
+ //#endregion
package/dist/model.js ADDED
@@ -0,0 +1,53 @@
1
+ import { streamText } from "ai";
2
+ //#region src/model.ts
3
+ /**
4
+ * `system` as instructions whose first part, `prefix`, a provider may cache:
5
+ * two system messages, the first marked as a cache breakpoint for providers
6
+ * that need one (Anthropic). Providers that cache a repeated prefix on their
7
+ * own (OpenAI, DeepSeek, Google) read the two as one. Without a prefix that
8
+ * `system` starts with, `system` alone.
9
+ */
10
+ function cachedInstructions(system, prefix) {
11
+ if (prefix === "" || !system.startsWith(prefix)) return system;
12
+ return [{
13
+ content: prefix,
14
+ providerOptions: { anthropic: { cacheControl: { type: "ephemeral" } } },
15
+ role: "system"
16
+ }, {
17
+ content: system.slice(prefix.length),
18
+ role: "system"
19
+ }];
20
+ }
21
+ /**
22
+ * A `model` for `createPickleServer` from an AI SDK model: document-mode
23
+ * sessions and chat repairs stream through `streamText`, and token usage
24
+ * reaches `onTurn`. The prompt's stable start is marked for the provider's
25
+ * cache (`cachedInstructions`).
26
+ */
27
+ function aiSdkModel(model, settings = {}) {
28
+ return async function* (request) {
29
+ const result = streamText({
30
+ ...settings,
31
+ abortSignal: request.signal,
32
+ instructions: cachedInstructions(request.system, request.systemPrefix),
33
+ messages: request.messages.map((message) => ({
34
+ content: message.content,
35
+ role: message.role
36
+ })),
37
+ model
38
+ });
39
+ for await (const part of result.stream) if (part.type === "text-delta") yield part.text;
40
+ else if (part.type === "error") throw part.error;
41
+ const usage = await result.usage;
42
+ const sent = {
43
+ inputTokens: usage.inputTokens ?? 0,
44
+ outputTokens: usage.outputTokens ?? 0
45
+ };
46
+ const details = usage.inputTokenDetails;
47
+ if (details.cacheReadTokens !== void 0) sent.cacheReadTokens = details.cacheReadTokens;
48
+ if (details.cacheWriteTokens !== void 0) sent.cacheWriteTokens = details.cacheWriteTokens;
49
+ return { usage: sent };
50
+ };
51
+ }
52
+ //#endregion
53
+ export { aiSdkModel, cachedInstructions };
@@ -0,0 +1,66 @@
1
+ import { z } from "zod";
2
+ //#region src/schemas.d.ts
3
+ /**
4
+ * Schemas for `validateUIMessages<StoredPickleUIMessage>({ messages,
5
+ * dataSchemas })`, to check a conversation the browser sent or one read
6
+ * from storage. A pickle's program is dropped, never checked: the server
7
+ * reads only `id` and `source`, and a source is only ever compiled.
8
+ */
9
+ export declare const pickleDataSchemas: {
10
+ pickle: z.ZodObject<{
11
+ diagnostics: z.ZodArray<z.ZodObject<{
12
+ code: z.ZodString;
13
+ fix: z.ZodExactOptional<z.ZodObject<{
14
+ edits: z.ZodArray<z.ZodObject<{
15
+ span: z.ZodObject<{
16
+ column: z.ZodExactOptional<z.ZodNumber>;
17
+ end: z.ZodExactOptional<z.ZodNumber>;
18
+ line: z.ZodNumber;
19
+ start: z.ZodExactOptional<z.ZodNumber>;
20
+ }, z.core.$strip>;
21
+ text: z.ZodString;
22
+ }, z.core.$strip>>;
23
+ title: z.ZodString;
24
+ }, z.core.$strip>>;
25
+ message: z.ZodString;
26
+ severity: z.ZodEnum<{
27
+ error: "error";
28
+ hint: "hint";
29
+ info: "info";
30
+ warning: "warning";
31
+ }>;
32
+ span: z.ZodObject<{
33
+ column: z.ZodExactOptional<z.ZodNumber>;
34
+ end: z.ZodExactOptional<z.ZodNumber>;
35
+ line: z.ZodNumber;
36
+ start: z.ZodExactOptional<z.ZodNumber>;
37
+ }, z.core.$strip>;
38
+ }, z.core.$strip>>;
39
+ error: z.ZodExactOptional<z.ZodObject<{
40
+ code: z.ZodEnum<{
41
+ BAD_INPUT: "BAD_INPUT";
42
+ CONFLICT: "CONFLICT";
43
+ DECLINED: "DECLINED";
44
+ FORBIDDEN: "FORBIDDEN";
45
+ INTERNAL: "INTERNAL";
46
+ NETWORK: "NETWORK";
47
+ NOT_FOUND: "NOT_FOUND";
48
+ RATE_LIMITED: "RATE_LIMITED";
49
+ UNAUTHORIZED: "UNAUTHORIZED";
50
+ UNAVAILABLE: "UNAVAILABLE";
51
+ UNKNOWN_PROCEDURE: "UNKNOWN_PROCEDURE";
52
+ }>;
53
+ message: z.ZodString;
54
+ }, z.core.$strip>>;
55
+ id: z.ZodString;
56
+ key: z.ZodString;
57
+ source: z.ZodString;
58
+ status: z.ZodEnum<{
59
+ done: "done";
60
+ repairing: "repairing";
61
+ streaming: "streaming";
62
+ }>;
63
+ version: z.ZodNumber;
64
+ }, z.core.$strip>;
65
+ };
66
+ //#endregion
@@ -0,0 +1,51 @@
1
+ import { z } from "zod";
2
+ import { pickleErrorCodes } from "@jigs.design/runtime";
3
+ //#region src/schemas.ts
4
+ const span = z.object({
5
+ column: z.number().exactOptional(),
6
+ end: z.number().exactOptional(),
7
+ line: z.number(),
8
+ start: z.number().exactOptional()
9
+ });
10
+ const storedBlock = z.object({
11
+ diagnostics: z.array(z.object({
12
+ code: z.string(),
13
+ fix: z.object({
14
+ edits: z.array(z.object({
15
+ span,
16
+ text: z.string()
17
+ })),
18
+ title: z.string()
19
+ }).exactOptional(),
20
+ message: z.string(),
21
+ severity: z.enum([
22
+ "error",
23
+ "hint",
24
+ "info",
25
+ "warning"
26
+ ]),
27
+ span
28
+ })),
29
+ error: z.object({
30
+ code: z.enum(pickleErrorCodes),
31
+ message: z.string()
32
+ }).exactOptional(),
33
+ id: z.string(),
34
+ key: z.string(),
35
+ source: z.string(),
36
+ status: z.enum([
37
+ "streaming",
38
+ "repairing",
39
+ "done"
40
+ ]),
41
+ version: z.number()
42
+ });
43
+ /**
44
+ * Schemas for `validateUIMessages<StoredPickleUIMessage>({ messages,
45
+ * dataSchemas })`, to check a conversation the browser sent or one read
46
+ * from storage. A pickle's program is dropped, never checked: the server
47
+ * reads only `id` and `source`, and a source is only ever compiled.
48
+ */
49
+ const pickleDataSchemas = { pickle: storedBlock };
50
+ //#endregion
51
+ export { pickleDataSchemas, storedBlock };
@@ -0,0 +1,58 @@
1
+ import { ChatBlock } from "@jigs.design/runtime";
2
+ import { UIMessage } from "ai";
3
+ //#region src/stored.d.ts
4
+ /** A pickle's `data-pickle` part: one fenced block of a reply, compiled. */
5
+ export type PickleBlockData = ChatBlock;
6
+ /**
7
+ * A pickle as the browser sends it back and as you store it: everything but
8
+ * the program. Only `revivePickles` turns it back into a `PickleBlockData`,
9
+ * by compiling `source` again.
10
+ */
11
+ export type StoredPickleBlock = Omit<PickleBlockData, "program">;
12
+ /**
13
+ * The data parts chat mode adds to a message. A type, not an interface: the
14
+ * AI SDK's data types need an implicit index signature.
15
+ */
16
+ export type PickleDataTypes = {
17
+ pickle: PickleBlockData;
18
+ };
19
+ /** A `useChat` message whose replies may hold pickles. */
20
+ export type PickleUIMessage<TMetadata = unknown> = UIMessage<TMetadata, PickleDataTypes>;
21
+ /**
22
+ * A conversation as the server receives it and as you store it: its pickles
23
+ * carry no programs. `validateUIMessages` with `pickleDataSchemas` returns
24
+ * this shape.
25
+ */
26
+ export type StoredPickleUIMessage<TMetadata = unknown> = UIMessage<TMetadata, {
27
+ pickle: StoredPickleBlock;
28
+ }>;
29
+ /** A message part, with a `data-pickle` part's data replaced by `TBlock`. */
30
+ type WithBlock<TPart, TBlock> = TPart extends {
31
+ type: "data-pickle";
32
+ } ? Omit<TPart, "data"> & {
33
+ data: TBlock;
34
+ } : TPart;
35
+ /**
36
+ * `TMessage` with each pickle's data replaced by `TBlock`, and every other
37
+ * part, the metadata included, as it was.
38
+ */
39
+ export type WithPickleBlocks<TMessage extends UIMessage, TBlock> = Omit<TMessage, "parts"> & {
40
+ parts: Array<WithBlock<TMessage["parts"][number], TBlock>>;
41
+ };
42
+ /**
43
+ * The messages without their pickles' programs: for the body `useChat`
44
+ * posts (`prepareSendMessagesRequest`), and for storing a conversation. The
45
+ * server reads only each block's `id` and `source`. Every other part, and
46
+ * every other field of a block, is kept as it is.
47
+ */
48
+ export declare function withoutPrograms<TMessage extends UIMessage>(messages: ReadonlyArray<TMessage>): Array<WithPickleBlocks<TMessage, StoredPickleBlock>>;
49
+ /**
50
+ * The end of a long conversation, for the body `useChat` posts: the last
51
+ * `keep` messages, from a user message on. Every earlier pickle comes first,
52
+ * in one assistant message of their `data-pickle` parts, so the model still
53
+ * sees each one and edits and versions continue from them; earlier prose is
54
+ * left out. A conversation no longer than `keep` is returned as it is.
55
+ */
56
+ export declare function recentMessages<TMessage extends UIMessage>(messages: ReadonlyArray<TMessage>, keep?: number): Array<TMessage>;
57
+ //#endregion
58
+ export {};
package/dist/stored.js ADDED
@@ -0,0 +1,50 @@
1
+ //#region src/stored.ts
2
+ /** Whether `part` is a pickle whose block still holds its program. */
3
+ function hasProgram(part) {
4
+ return part.type === "data-pickle" && "data" in part && typeof part.data === "object" && part.data !== null && "program" in part.data;
5
+ }
6
+ /**
7
+ * The messages without their pickles' programs: for the body `useChat`
8
+ * posts (`prepareSendMessagesRequest`), and for storing a conversation. The
9
+ * server reads only each block's `id` and `source`. Every other part, and
10
+ * every other field of a block, is kept as it is.
11
+ */
12
+ function withoutPrograms(messages) {
13
+ return messages.map((message) => ({
14
+ ...message,
15
+ parts: message.parts.map((part) => {
16
+ if (!hasProgram(part)) return part;
17
+ const { program: _program, ...data } = part.data;
18
+ return {
19
+ ...part,
20
+ data
21
+ };
22
+ })
23
+ }));
24
+ }
25
+ /** How many messages `recentMessages` keeps by default. */
26
+ const RECENT = 20;
27
+ /**
28
+ * The end of a long conversation, for the body `useChat` posts: the last
29
+ * `keep` messages, from a user message on. Every earlier pickle comes first,
30
+ * in one assistant message of their `data-pickle` parts, so the model still
31
+ * sees each one and edits and versions continue from them; earlier prose is
32
+ * left out. A conversation no longer than `keep` is returned as it is.
33
+ */
34
+ function recentMessages(messages, keep = RECENT) {
35
+ if (messages.length <= keep) return [...messages];
36
+ let start = messages.length - keep;
37
+ const nextUser = messages.findIndex((message, index) => index >= start && message.role === "user");
38
+ if (nextUser !== -1) start = nextUser;
39
+ const earlier = messages.slice(0, start).filter((message) => message.role === "assistant");
40
+ const pickles = earlier.flatMap((message) => message.parts.filter((part) => part.type === "data-pickle"));
41
+ const recent = messages.slice(start);
42
+ const [first] = earlier;
43
+ if (first === void 0 || pickles.length === 0) return recent;
44
+ return [{
45
+ ...first,
46
+ parts: pickles
47
+ }, ...recent];
48
+ }
49
+ //#endregion
50
+ export { recentMessages, withoutPrograms };
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
2
  "name": "@jigs.design/ai-sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.1",
4
+ "description": "Pickle on the AI SDK: a model function for createPickleServer, and chat mode for useChat.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/yoamomonstruos/pickle.git",
9
+ "directory": "packages/ai-sdk"
10
+ },
11
+ "type": "module",
12
+ "sideEffects": false,
13
+ "files": [
14
+ "dist",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ },
23
+ "./client": {
24
+ "types": "./dist/client.d.ts",
25
+ "import": "./dist/client.js"
26
+ }
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "dependencies": {
32
+ "@jigs.design/runtime": "0.1.1",
33
+ "@jigs.design/server": "0.1.1"
34
+ },
35
+ "peerDependencies": {
36
+ "ai": "^7.0.0",
37
+ "zod": "^4.0.0"
38
+ },
39
+ "devDependencies": {
40
+ "@ai-sdk/provider": "4.0.27",
41
+ "ai": "7.0.140",
42
+ "zod": "4.6.7",
43
+ "@jigs.design/host": "0.1.1",
44
+ "@jigs.design/catalog": "0.1.1"
45
+ }
6
46
  }