Skip to content

Commit f91ccee

Browse files
committed
Release v0.2.29
1 parent 0dc81b1 commit f91ccee

47 files changed

Lines changed: 1400 additions & 1059 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎RELEASE.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
2-
"version": "0.2.28",
2+
"version": "0.2.29",
33
"license": "FSL-1.1-ALv2",
44
"baselineSha256": "7847f65a9b20b366a60d52d19ca3cd6bdd1bb09b8cb506659a270f27a21d8877",
55
"e2b": {
66
"recipeSha256": "761809ee2efa2b6c750451ace29283cbb8e55b6ce3b5b14c2bb4509df71df801",
77
"profileSha256": "414dbecee2d47de36016ea05c736e2edff3a0deb298babf923887ea184a01c10"
88
},
99
"dxd": {
10-
"url": "https://github.com/dxcode-dev/dxcode/releases/download/v0.2.28/dxd-linux-x64",
10+
"url": "https://github.com/dxcode-dev/dxcode/releases/download/v0.2.29/dxd-linux-x64",
1111
"sha256": "76f424e6253874df89ceeb8f42d2f2a67ff3024d4116639cec5d04624ab4f345"
1212
},
1313
"toolchain": {

‎alchemy.run.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1038,7 +1038,12 @@ export default Alchemy.Stack(
10381038
...(configuration.environment !== "staging"
10391039
? { DX_PREVIEW_DXD_SHA256: configuration.dxdChecksum }
10401040
: {}),
1041-
DX_PREVIEW_WORKER_ID: worker.workerId,
1041+
// Alchemy knows a Worker's ID before an update deploys it; pairing
1042+
// it with the hash makes Verify wait for the new version.
1043+
DX_PREVIEW_WORKER_ID: Output.map(
1044+
Output.all(worker.workerId, worker.hash),
1045+
([workerId]) => workerId,
1046+
),
10421047
DX_PREVIEW_BOOTSTRAP: Output.map(bootstrap.hash.input, () => "ready"),
10431048
},
10441049
memo: false,

‎apps/core/package.json‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@dx/core",
33
"private": true,
4-
"version": "0.2.28",
4+
"version": "0.2.29",
55
"license": "FSL-1.1-ALv2",
66
"type": "module",
77
"scripts": {
@@ -20,7 +20,7 @@
2020
"@dx/api": "workspace:^",
2121
"@dx/domain": "workspace:^",
2222
"@effect/sql-d1": "4.0.5",
23-
"@flue/runtime": "file:../../third-party/flue/runtime-e7bef20be3c74f4ffdeeed83b1fe161a1fb5f0c8.tgz",
23+
"@flue/runtime": "file:../../third-party/flue/runtime-33a2cf7e3796a7f3960a24455f3c5bb57537ee13.tgz",
2424
"@jitl/quickjs-wasmfile-release-sync": "0.32.0",
2525
"@logtape/logtape": "2.3.1",
2626
"@modelcontextprotocol/client": "2.0.0",
@@ -42,9 +42,9 @@
4242
"@cloudflare/vite-plugin": "1.53.0",
4343
"@cloudflare/vitest-plugin": "1.0.0",
4444
"@cloudflare/workers-types": "5.20260820.1",
45-
"@earendil-works/pi-ai": "0.85.1",
46-
"@flue/sdk": "file:../../third-party/flue/sdk-e7bef20be3c74f4ffdeeed83b1fe161a1fb5f0c8.tgz",
47-
"@flue/vite": "file:../../third-party/flue/vite-e7bef20be3c74f4ffdeeed83b1fe161a1fb5f0c8.tgz",
45+
"@earendil-works/pi-ai": "1.1.0",
46+
"@flue/sdk": "file:../../third-party/flue/sdk-33a2cf7e3796a7f3960a24455f3c5bb57537ee13.tgz",
47+
"@flue/vite": "file:../../third-party/flue/vite-33a2cf7e3796a7f3960a24455f3c5bb57537ee13.tgz",
4848
"@msw/cloudflare": "^0.0.1",
4949
"@types/ws": "8.18.1",
5050
"@valibot/to-json-schema": "1.7.1",

‎apps/core/src/claude-code/claude-code.test.ts‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import {
99
} from "node:fs/promises";
1010
import { tmpdir } from "node:os";
1111
import { join } from "node:path";
12-
import type {
13-
AssistantMessage,
14-
AssistantMessageEvent,
15-
Context,
12+
import {
13+
type AssistantMessage,
14+
type AssistantMessageEvent,
15+
type Context,
16+
normalizeContext,
1617
} from "@earendil-works/pi-ai";
1718
import type { Sandbox } from "@flue/runtime";
1819
import { local } from "@flue/runtime/node";
@@ -261,9 +262,11 @@ const claudeCode = (
261262
/** One Flue model call; resolves with its final event. */
262263
answer: async (context: Context, signal?: AbortSignal) => {
263264
const events: AssistantMessageEvent[] = [];
264-
for await (const next of provider.streamSimple(model, context, {
265-
...(signal === undefined ? {} : { signal }),
266-
}))
265+
for await (const next of provider.streamSimple(
266+
model,
267+
normalizeContext(context),
268+
{ ...(signal === undefined ? {} : { signal }) },
269+
))
267270
events.push(next);
268271
const last = events.at(-1);
269272
if (last === undefined) throw new Error("Expected an event.");

‎apps/core/src/claude-code/provider.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import {
1414
type Context,
1515
createAssistantMessageEventStream,
1616
createProvider,
17+
getCurrentSystemPrompt,
18+
type JsonObject,
1719
type Model,
1820
type SimpleStreamOptions,
1921
type Usage,
@@ -133,7 +135,7 @@ const contentFor = (block: ClaudeCodeBlock): Content => {
133135
type: "toolCall",
134136
id: block.id,
135137
name: call.name,
136-
arguments: call.arguments,
138+
arguments: call.arguments as JsonObject,
137139
};
138140
}
139141
}
@@ -628,7 +630,8 @@ export const createClaudeCodeProvider = (deps: ClaudeCodeProviderDeps) => {
628630
effort: claudeCodeEffort(
629631
options?.reasoning ?? CLAUDE_CODE_DEFAULT_EFFORT,
630632
),
631-
systemPrompt: context.systemPrompt ?? "",
633+
// pi carries the prompt as transcript system messages.
634+
systemPrompt: getCurrentSystemPrompt(context.messages),
632635
input: input(),
633636
token: credential.token,
634637
tools: claudeCodeDxToolList(threadId),

‎apps/core/src/cloudflare.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,11 @@ if (
5454
...getBuiltinProviders(),
5555
"cloudflare",
5656
"github-copilot",
57+
"azure-openai-responses",
5758
]) {
58-
if (providerId === "cloudflare-workers-ai") continue;
59+
// dx's canonical ids for these pi providers are registered above.
60+
if (providerId === "cloudflare-workers-ai" || providerId === "azure")
61+
continue;
5962
const { provider, options } = createDxModelRoutingProvider(
6063
providerId,
6164
deps,

‎apps/core/src/execution/e2b/default-tools.test.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { getCurrentTools } from "@earendil-works/pi-ai";
12
import {
23
fauxAssistantMessage,
34
fauxProvider,
@@ -13,7 +14,7 @@ it("exposes only dx's workspace tools to the model", async () => {
1314
let toolNames: string[] = [];
1415
provider.setResponses([
1516
(context) => {
16-
toolNames = context.tools?.map((tool) => tool.name) ?? [];
17+
toolNames = getCurrentTools(context.messages).map((tool) => tool.name);
1718
return fauxAssistantMessage("done");
1819
},
1920
]);

‎apps/core/src/observability/flue.ts‎

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,42 @@ const correlations = (event: FlueObservation) => ({
3030
: { submissionId: event.submissionId }),
3131
});
3232

33+
// Failure text from the provider or runtime, never model output. Bounded so
34+
// a provider that echoes a large request cannot flood the log.
35+
const ERROR_TEXT_LIMIT = 2_000;
36+
const bounded = (text: string | undefined) =>
37+
text === undefined || text === ""
38+
? undefined
39+
: text.slice(0, ERROR_TEXT_LIMIT);
40+
41+
const failure = (error: {
42+
type?: string;
43+
name?: string;
44+
code?: string;
45+
message?: string;
46+
details?: string;
47+
}) => {
48+
const fields = {
49+
errorType: error.type,
50+
errorName: error.name,
51+
errorCode: error.code,
52+
errorMessage: bounded(error.message),
53+
errorDetails: bounded(error.details),
54+
};
55+
return Object.fromEntries(
56+
Object.entries(fields).filter(([, value]) => value !== undefined),
57+
);
58+
};
59+
60+
const unknownFailure = (error: unknown) =>
61+
error instanceof Error
62+
? failure({ name: error.name, message: error.message })
63+
: typeof error === "string"
64+
? failure({ message: error })
65+
: error === undefined
66+
? {}
67+
: failure({ message: JSON.stringify(error) });
68+
3369
const observeFlueEvent = (
3470
event: FlueObservation,
3571
context: FlueEventContext,
@@ -154,10 +190,30 @@ const observeFlueEvent = (
154190
});
155191
return persistence;
156192
case "submission_settled":
157-
agentLifecycleLogger.info("Flue submission settled.", {
193+
if (event.outcome === "failed")
194+
agentLifecycleLogger.error("Flue submission failed.", {
195+
event: event.type,
196+
...correlations(event),
197+
outcome: event.outcome,
198+
...(event.error === undefined ? {} : failure(event.error)),
199+
});
200+
else
201+
agentLifecycleLogger.info("Flue submission settled.", {
202+
event: event.type,
203+
...correlations(event),
204+
outcome: event.outcome,
205+
});
206+
return persistence;
207+
case "submission_recovery":
208+
agentLifecycleLogger.warn("Flue submission recovery step failed.", {
158209
event: event.type,
159210
...correlations(event),
211+
kind: event.kind,
212+
operation: event.operation,
160213
outcome: event.outcome,
214+
attemptCount: event.attemptCount,
215+
maxAttempts: event.maxAttempts,
216+
...(event.error === undefined ? {} : failure(event.error)),
161217
});
162218
return persistence;
163219
case "turn_start":
@@ -182,7 +238,7 @@ const observeFlueEvent = (
182238
return persistence;
183239
case "turn": {
184240
const usage = event.response.usage;
185-
agentModelLogger.info("Flue model turn completed.", {
241+
const fields = {
186242
event: event.type,
187243
...correlations(event),
188244
turnId: event.turnId,
@@ -191,6 +247,17 @@ const observeFlueEvent = (
191247
requestedModel: event.request.requestedModel,
192248
durationMs: event.durationMs,
193249
isError: event.isError,
250+
...(event.isError
251+
? {
252+
finishReason: event.response.finishReason,
253+
providerFinishReason: event.response.providerFinishReason,
254+
// Flue copies an in-band failure, such as Claude Code's, from
255+
// the assistant message into this error.
256+
...(event.response.error === undefined
257+
? {}
258+
: failure(event.response.error)),
259+
}
260+
: {}),
194261
...(usage === undefined
195262
? {}
196263
: {
@@ -200,7 +267,10 @@ const observeFlueEvent = (
200267
cacheWriteTokens: usage.cacheWrite,
201268
totalTokens: usage.totalTokens,
202269
}),
203-
});
270+
};
271+
if (event.isError)
272+
agentModelLogger.error("Flue model turn failed.", fields);
273+
else agentModelLogger.info("Flue model turn completed.", fields);
204274
return persistence;
205275
}
206276
case "compaction_start":
@@ -211,16 +281,23 @@ const observeFlueEvent = (
211281
estimatedTokens: event.estimatedTokens,
212282
});
213283
return persistence;
214-
case "compaction":
215-
agentModelLogger.info("Flue compaction completed.", {
284+
case "compaction": {
285+
const fields = {
216286
event: event.type,
217287
...correlations(event),
218288
isError: event.isError,
219289
messagesBefore: event.messagesBefore,
220290
messagesAfter: event.messagesAfter,
221291
durationMs: event.durationMs,
222-
});
292+
};
293+
if (event.isError)
294+
agentModelLogger.error("Flue compaction failed.", {
295+
...fields,
296+
...unknownFailure(event.error),
297+
});
298+
else agentModelLogger.info("Flue compaction completed.", fields);
223299
return persistence;
300+
}
224301
case "tool_start":
225302
agentToolLogger.info("Flue tool execution started.", {
226303
event: event.type,

‎apps/core/src/runtime/model-fixtures.test.ts‎

Lines changed: 48 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import type { AssistantMessageEvent, Context } from "@earendil-works/pi-ai";
1+
import {
2+
type AssistantMessageEvent,
3+
normalizeContext,
4+
} from "@earendil-works/pi-ai";
25
import { describe, expect, it } from "vitest";
36
import {
47
createLocalModelFixtureProvider,
@@ -7,15 +10,16 @@ import {
710
localModelFixtureForPrompt,
811
} from "./model-fixtures.js";
912

10-
const context = (prompt: string): Context => ({
11-
messages: [
12-
{
13-
role: "user",
14-
content: [{ type: "text", text: prompt }],
15-
timestamp: 0,
16-
},
17-
],
18-
});
13+
const context = (prompt: string) =>
14+
normalizeContext({
15+
messages: [
16+
{
17+
role: "user",
18+
content: [{ type: "text", text: prompt }],
19+
timestamp: 0,
20+
},
21+
],
22+
});
1923

2024
describe("local model fixtures", () => {
2125
it("fails unknown prompts closed before any provider transport", () => {
@@ -66,20 +70,23 @@ describe("local model fixtures", () => {
6670
});
6771

6872
const continued: AssistantMessageEvent[] = [];
69-
for await (const event of provider.stream(model, {
70-
messages: [
71-
...context(LOCAL_MODEL_FIXTURE_PROMPTS.toolContinuation).messages,
72-
toolUse.message,
73-
{
74-
role: "toolResult",
75-
toolCallId: "dx-local-fixture-write",
76-
toolName: "create_file",
77-
content: [{ type: "text", text: "Successfully wrote 25 bytes" }],
78-
isError: false,
79-
timestamp: 0,
80-
},
81-
],
82-
}))
73+
for await (const event of provider.stream(
74+
model,
75+
normalizeContext({
76+
messages: [
77+
...context(LOCAL_MODEL_FIXTURE_PROMPTS.toolContinuation).messages,
78+
toolUse.message,
79+
{
80+
role: "toolResult",
81+
toolCallId: "dx-local-fixture-write",
82+
toolName: "create_file",
83+
content: [{ type: "text", text: "Successfully wrote 25 bytes" }],
84+
isError: false,
85+
timestamp: 0,
86+
},
87+
],
88+
}),
89+
))
8390
continued.push(event);
8491
expect(continued.at(-1)).toMatchObject({
8592
type: "done",
@@ -95,20 +102,23 @@ describe("local model fixtures", () => {
95102
});
96103

97104
expect(() =>
98-
provider.stream(model, {
99-
messages: [
100-
...context(LOCAL_MODEL_FIXTURE_PROMPTS.toolContinuation).messages,
101-
toolUse.message,
102-
{
103-
role: "toolResult",
104-
toolCallId: "dx-local-fixture-write",
105-
toolName: "create_file",
106-
content: [{ type: "text", text: "write failed" }],
107-
isError: true,
108-
timestamp: 0,
109-
},
110-
],
111-
}),
105+
provider.stream(
106+
model,
107+
normalizeContext({
108+
messages: [
109+
...context(LOCAL_MODEL_FIXTURE_PROMPTS.toolContinuation).messages,
110+
toolUse.message,
111+
{
112+
role: "toolResult",
113+
toolCallId: "dx-local-fixture-write",
114+
toolName: "create_file",
115+
content: [{ type: "text", text: "write failed" }],
116+
isError: true,
117+
timestamp: 0,
118+
},
119+
],
120+
}),
121+
),
112122
).toThrow(LocalModelFixtureMismatch);
113123
});
114124
});

0 commit comments

Comments
 (0)