@aexhq/sdk 0.83.0 → 0.84.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/README.md +74 -2
- package/dist/index.d.ts +25 -4
- package/dist/index.js +31 -5
- package/dist/session.d.ts +22 -0
- package/dist/session.js +47 -0
- package/dist/structured-output.d.ts +17 -0
- package/dist/structured-output.js +83 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ tools, send messages, and read saved conversations without operating a Brain ser
|
|
|
8
8
|
Create a key in the [dashboard](https://aex.dev/dashboard). With Node.js 22 or newer, install:
|
|
9
9
|
|
|
10
10
|
```sh
|
|
11
|
-
npm install @aexhq/sdk@0.
|
|
11
|
+
npm install @aexhq/sdk@0.84.1 @aexhq/agentloop-pi@7.2.3 zod@4.4.3
|
|
12
12
|
```
|
|
13
13
|
|
|
14
14
|
Set `AEX_API_KEY` and `OPENAI_API_KEY` in your server environment. Save as `order.mjs` and
|
|
@@ -56,13 +56,85 @@ The SDK uses Brain's session API and extension helpers. You can import `tool`, `
|
|
|
56
56
|
|
|
57
57
|
- [Sessions](https://aex.dev/brain/docs/concepts/sessions): send, submit, stream, reconnect and stop.
|
|
58
58
|
- [Tools](https://aex.dev/brain/docs/guides/write-a-tool): application functions and packaged tools.
|
|
59
|
-
- [Structured output](
|
|
59
|
+
- [Structured output](#structured-output): validated JSON answers.
|
|
60
60
|
- [Managed environments](https://github.com/aexhq/aex/blob/main/docs/environments.md): hosted tools and workspaces.
|
|
61
61
|
- [Attachments](https://github.com/aexhq/aex/blob/main/docs/attachments.md): upload images and PDFs.
|
|
62
62
|
|
|
63
63
|
Use `ctx.finish(value)` to complete a tool. Use `aex.close()` in `finally` to close client
|
|
64
64
|
connections. Closing the client keeps stored sessions; tools in your process still require it.
|
|
65
65
|
|
|
66
|
+
## Structured output
|
|
67
|
+
|
|
68
|
+
Ask for a typed application answer on one send. Before ending the session, pass a Zod
|
|
69
|
+
schema alongside the message:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
const answer = await session.send("Return the order status", {
|
|
73
|
+
output: { type: z.object({ id: z.string(), status: z.string() }), maxRetries: 2 },
|
|
74
|
+
});
|
|
75
|
+
console.log(answer.status);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Aex adds schema instructions to the prompt, reads that completed turn's assistant output,
|
|
79
|
+
parses JSON and validates it locally. The result has the schema's inferred output type.
|
|
80
|
+
Ordinary sends still return session state. The
|
|
81
|
+
[executable example](../../examples/structured-output.mjs) shows a complete session;
|
|
82
|
+
the [hosted guide](https://aex.dev/docs#structured-output) describes the same contract.
|
|
83
|
+
|
|
84
|
+
`maxRetries` counts additional correction turns and defaults to two. Zero validates one
|
|
85
|
+
candidate. Invalid JSON or Zod issues produce a follow-up asking for a complete corrected
|
|
86
|
+
answer. Fences and surrounding prose fail parsing. Normal Zod semantics apply: ordinary
|
|
87
|
+
objects strip unknown properties, strict objects reject them, and defaults, transforms and
|
|
88
|
+
async refinements run locally. The prompt describes the input shape; the returned value is
|
|
89
|
+
the parsed output. Unrepresentable schemas fail before sending, and custom-validator
|
|
90
|
+
exceptions propagate without corrections.
|
|
91
|
+
|
|
92
|
+
Exhaustion throws `StructuredOutputError` with `attempts`, `lastOutput` and `issues`.
|
|
93
|
+
Provider, transport, failed-turn and unsupported-output errors do not trigger corrections.
|
|
94
|
+
The loop must emit an Agentloop-origin `output_emitted` assistant message, as Pi and Codex do.
|
|
95
|
+
Aex reads the last such message within the exact completed turn, including idempotent
|
|
96
|
+
receipts from earlier history. It does not scrape internal model calls or infer refusals
|
|
97
|
+
from arbitrary text. No provider response format is set or cleared; avoid conflicting
|
|
98
|
+
session-level `responseFormat` settings.
|
|
99
|
+
|
|
100
|
+
Each attempt is an ordinary durable turn and can invoke Tools. Asking the agent not to
|
|
101
|
+
repeat actions is not enforcement. The caller must remain alive; rejected answers stay in
|
|
102
|
+
history and streams, and local validation failure does not rewrite a completed server turn.
|
|
103
|
+
Use exclusive ownership of sends during a typed operation. Overlapping send/submit calls
|
|
104
|
+
on the same wrapper fail; other handles and processes need application coordination.
|
|
105
|
+
An optional top-level `signal` cancels the active turn, aborts event reads and stops further
|
|
106
|
+
corrections. Async validators finish before their result is checked for cancellation.
|
|
107
|
+
|
|
108
|
+
The initial turn uses the supplied `idempotencyKey`; corrections use distinct derived keys.
|
|
109
|
+
Re-entry reuses completed turns when prompts and validation feedback are identical. This
|
|
110
|
+
is not an atomic server operation; nondeterministic validation can conflict on replay.
|
|
111
|
+
|
|
112
|
+
For work submitted by a short-lived caller, configure the official Agentloop with
|
|
113
|
+
`output: { schema: z.toJSONSchema(answerType), maxCorrections: 2 }` and use `submit()`.
|
|
114
|
+
That separate extension policy validates inside the hosted turn and prohibits Tools during
|
|
115
|
+
correction. It supports JSON Schema, not arbitrary local Zod refinements or transforms.
|
|
116
|
+
|
|
117
|
+
### Migrating from Brain SDK typed sends
|
|
118
|
+
|
|
119
|
+
Brain SDK 0.34 removes typed sends and the corresponding types and errors. Aex SDK 0.84
|
|
120
|
+
owns them. Existing Aex `send(..., { output })` calls keep the same syntax and behavior.
|
|
121
|
+
Import `StructuredSendOptions` and `StructuredOutputError` from `@aexhq/sdk`.
|
|
122
|
+
Use Pi/Codex 7.2.3 and extension packages pinned to Brain 0.34 with this SDK.
|
|
123
|
+
Mixing exact Brain dependency versions creates different TypeScript extension brands.
|
|
124
|
+
|
|
125
|
+
An existing standalone Brain handle can use Aex's policy without changing servers:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
import { AexSessionHandle } from "@aexhq/sdk";
|
|
129
|
+
const session = new AexSessionHandle(await brain.sessions.get(sessionId));
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`Aex` and `AexSessionHandle` use composition, so they are not instances of the upstream
|
|
133
|
+
`Brain` and `SessionHandle` classes. Use `AexSessionHandle` for explicit product-handle
|
|
134
|
+
annotations. Neutral Brain classes and extension helpers are still re-exported with their
|
|
135
|
+
original identities; `withToken()` still returns a raw Brain client. Stored sessions,
|
|
136
|
+
server contracts, native model formats and Tool schemas are unchanged.
|
|
137
|
+
|
|
66
138
|
## Account and usage
|
|
67
139
|
|
|
68
140
|
```js
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export * from "@aexhq/brain";
|
|
2
2
|
export type * from "./generated.js";
|
|
3
|
+
export { AexSessionHandle } from "./session.js";
|
|
4
|
+
export { StructuredOutputError } from "./structured-output.js";
|
|
5
|
+
export type { StructuredSendOptions } from "./structured-output.js";
|
|
3
6
|
import { Brain, type BrainOptions } from "@aexhq/brain";
|
|
7
|
+
import { AexSessionHandle } from "./session.js";
|
|
4
8
|
import type { EnvironmentCatalog, TokenUsage } from "./generated.js";
|
|
5
9
|
import type { Attachment, Account, Usage, ApiKey, IssuedKey, KeyInput, LoginGrantInput, LoginGrant, LoginExchange, AccountSession, Wallet, BillingSettings, LedgerPage, Topup, TopupInput, Refund, RefundInput, SyncPayment } from "./generated.js";
|
|
6
10
|
export type AexConnection = Omit<BrainOptions, "token" | "baseUrl"> & {
|
|
@@ -15,11 +19,28 @@ export type AexOptions = AexConnection & {
|
|
|
15
19
|
accountToken: string;
|
|
16
20
|
apiKey?: never;
|
|
17
21
|
});
|
|
18
|
-
export declare class Aex
|
|
19
|
-
private readonly
|
|
22
|
+
export declare class Aex {
|
|
23
|
+
private readonly client;
|
|
20
24
|
constructor({ apiKey, accountToken, maxCostMicroUsd, ...options }: AexOptions);
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
get baseUrl(): string;
|
|
26
|
+
readonly sessions: Readonly<{
|
|
27
|
+
create: (options: import("@aexhq/brain").CreateSessionOptions, operation?: import("@aexhq/brain").OperationOptions | undefined) => Promise<AexSessionHandle>;
|
|
28
|
+
get: (sessionId: string, options?: {
|
|
29
|
+
tools?: readonly import("@aexhq/brain").PlacedTool[];
|
|
30
|
+
} | undefined) => Promise<AexSessionHandle>;
|
|
31
|
+
list: () => ReturnType<Brain["sessions"]["list"]>;
|
|
32
|
+
}>;
|
|
33
|
+
request<T>(...args: Parameters<Brain["request"]>): Promise<T>;
|
|
34
|
+
withToken(...args: Parameters<Brain["withToken"]>): ReturnType<Brain["withToken"]>;
|
|
35
|
+
close(): ReturnType<Brain["close"]>;
|
|
36
|
+
models(...args: Parameters<Brain["models"]>): ReturnType<Brain["models"]>;
|
|
37
|
+
stream(...args: Parameters<Brain["stream"]>): ReturnType<Brain["stream"]>;
|
|
38
|
+
streamPath(...args: Parameters<Brain["streamPath"]>): ReturnType<Brain["streamPath"]>;
|
|
39
|
+
admit(...args: Parameters<Brain["admit"]>): ReturnType<Brain["admit"]>;
|
|
40
|
+
admitAgentloop(...args: Parameters<Brain["admitAgentloop"]>): ReturnType<Brain["admitAgentloop"]>;
|
|
41
|
+
admitTool(...args: Parameters<Brain["admitTool"]>): ReturnType<Brain["admitTool"]>;
|
|
42
|
+
register(): ReturnType<Brain["register"]>;
|
|
43
|
+
credentials(): ReturnType<Brain["credentials"]>;
|
|
23
44
|
static exchangeLogin(input: LoginExchange, options?: AexConnection): Promise<AccountSession>;
|
|
24
45
|
readonly attachments: {
|
|
25
46
|
limits: () => Promise<import("./generated.js").AttachmentLimits>;
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
export * from "@aexhq/brain";
|
|
2
|
+
export { AexSessionHandle } from "./session.js";
|
|
3
|
+
export { StructuredOutputError } from "./structured-output.js";
|
|
2
4
|
import { Brain } from "@aexhq/brain";
|
|
5
|
+
import { AexSessionHandle } from "./session.js";
|
|
3
6
|
function nonnegativeInteger(value, name) {
|
|
4
7
|
if (!Number.isSafeInteger(value) || value < 0)
|
|
5
8
|
throw new TypeError(`${name} must be a nonnegative safe integer`);
|
|
6
9
|
}
|
|
7
|
-
|
|
10
|
+
class HostedBrain extends Brain {
|
|
8
11
|
maxCostMicroUsd;
|
|
9
|
-
constructor({
|
|
10
|
-
|
|
11
|
-
throw new TypeError("one apiKey or accountToken is required");
|
|
12
|
-
super({ baseUrl: "https://api.aex.dev", ...options, token: apiKey ?? accountToken });
|
|
12
|
+
constructor({ maxCostMicroUsd, ...options }) {
|
|
13
|
+
super(options);
|
|
13
14
|
if (maxCostMicroUsd !== undefined)
|
|
14
15
|
nonnegativeInteger(maxCostMicroUsd, "maxCostMicroUsd");
|
|
15
16
|
this.maxCostMicroUsd = maxCostMicroUsd;
|
|
@@ -21,6 +22,31 @@ export class Aex extends Brain {
|
|
|
21
22
|
headers.set("x-aex-max-cost-micro-usd", String(this.maxCostMicroUsd));
|
|
22
23
|
return super.request(method, path, body, idempotencyKey, contentType, signal, headers);
|
|
23
24
|
}
|
|
25
|
+
}
|
|
26
|
+
export class Aex {
|
|
27
|
+
client;
|
|
28
|
+
constructor({ apiKey, accountToken, maxCostMicroUsd, ...options }) {
|
|
29
|
+
if (!(apiKey || accountToken) || (apiKey && accountToken))
|
|
30
|
+
throw new TypeError("one apiKey or accountToken is required");
|
|
31
|
+
this.client = new HostedBrain({ baseUrl: "https://api.aex.dev", ...options, token: apiKey ?? accountToken, maxCostMicroUsd });
|
|
32
|
+
}
|
|
33
|
+
get baseUrl() { return this.client.baseUrl; }
|
|
34
|
+
sessions = Object.freeze({
|
|
35
|
+
create: async (...args) => new AexSessionHandle(await this.client.sessions.create(...args)),
|
|
36
|
+
get: async (...args) => new AexSessionHandle(await this.client.sessions.get(...args)),
|
|
37
|
+
list: () => this.client.sessions.list(),
|
|
38
|
+
});
|
|
39
|
+
request(...args) { return this.client.request(...args); }
|
|
40
|
+
withToken(...args) { return this.client.withToken(...args); }
|
|
41
|
+
close() { return this.client.close(); }
|
|
42
|
+
models(...args) { return this.client.models(...args); }
|
|
43
|
+
stream(...args) { return this.client.stream(...args); }
|
|
44
|
+
streamPath(...args) { return this.client.streamPath(...args); }
|
|
45
|
+
admit(...args) { return this.client.admit(...args); }
|
|
46
|
+
admitAgentloop(...args) { return this.client.admitAgentloop(...args); }
|
|
47
|
+
admitTool(...args) { return this.client.admitTool(...args); }
|
|
48
|
+
register() { return this.client.register(); }
|
|
49
|
+
credentials() { return this.client.credentials(); }
|
|
24
50
|
static exchangeLogin(input, options = {}) {
|
|
25
51
|
return new Brain({ baseUrl: "https://api.aex.dev", ...options }).request("POST", "/v1/auth/exchange", input);
|
|
26
52
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SessionHandle, SessionState, Schema, SchemaOutput, SendOptions, UserInput } from "@aexhq/brain";
|
|
2
|
+
import { type StructuredSendOptions } from "./structured-output.js";
|
|
3
|
+
export declare class AexSessionHandle {
|
|
4
|
+
private readonly session;
|
|
5
|
+
private activeSends;
|
|
6
|
+
private structuredSend;
|
|
7
|
+
constructor(session: SessionHandle);
|
|
8
|
+
get id(): string;
|
|
9
|
+
get state(): SessionState;
|
|
10
|
+
set state(value: SessionState);
|
|
11
|
+
get environments(): SessionHandle["environments"];
|
|
12
|
+
submit(...args: Parameters<SessionHandle["submit"]>): Promise<number>;
|
|
13
|
+
send<S extends Schema>(input: UserInput | string, operation: StructuredSendOptions<S>): Promise<SchemaOutput<S>>;
|
|
14
|
+
send(input: UserInput | string, operation?: SendOptions): Promise<SessionState>;
|
|
15
|
+
transcript(): ReturnType<SessionHandle["transcript"]>;
|
|
16
|
+
outcome(...args: Parameters<SessionHandle["outcome"]>): ReturnType<SessionHandle["outcome"]>;
|
|
17
|
+
events(...args: Parameters<SessionHandle["events"]>): ReturnType<SessionHandle["events"]>;
|
|
18
|
+
stream(...args: Parameters<SessionHandle["stream"]>): ReturnType<SessionHandle["stream"]>;
|
|
19
|
+
interrupt(...args: Parameters<SessionHandle["interrupt"]>): ReturnType<SessionHandle["interrupt"]>;
|
|
20
|
+
end(...args: Parameters<SessionHandle["end"]>): ReturnType<SessionHandle["end"]>;
|
|
21
|
+
delete(...args: Parameters<SessionHandle["delete"]>): ReturnType<SessionHandle["delete"]>;
|
|
22
|
+
}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { structuredOutput } from "./structured-output.js";
|
|
2
|
+
export class AexSessionHandle {
|
|
3
|
+
session;
|
|
4
|
+
activeSends = 0;
|
|
5
|
+
structuredSend = false;
|
|
6
|
+
constructor(session) {
|
|
7
|
+
this.session = session;
|
|
8
|
+
}
|
|
9
|
+
get id() { return this.session.id; }
|
|
10
|
+
get state() { return this.session.state; }
|
|
11
|
+
set state(value) { this.session.state = value; }
|
|
12
|
+
get environments() { return this.session.environments; }
|
|
13
|
+
async submit(...args) {
|
|
14
|
+
if (args[1] !== undefined && "output" in args[1])
|
|
15
|
+
throw new TypeError("submit requires structured output to run in the hosted Agentloop");
|
|
16
|
+
if (this.structuredSend)
|
|
17
|
+
throw new Error("structured output requires exclusive sends on this session handle");
|
|
18
|
+
return this.session.submit(...args);
|
|
19
|
+
}
|
|
20
|
+
async send(input, operation = {}) {
|
|
21
|
+
const normalized = typeof input === "string" ? { message: input } : input;
|
|
22
|
+
if (typeof normalized?.message !== "string" || normalized.message === "")
|
|
23
|
+
throw new TypeError("send needs a non-empty message");
|
|
24
|
+
const output = "output" in operation ? operation.output : undefined;
|
|
25
|
+
if (this.structuredSend || (output !== undefined && this.activeSends !== 0))
|
|
26
|
+
throw new Error("structured output requires exclusive sends on this session handle");
|
|
27
|
+
this.activeSends++;
|
|
28
|
+
this.structuredSend = output !== undefined;
|
|
29
|
+
const options = { signal: operation.signal, idempotencyKey: operation.idempotencyKey };
|
|
30
|
+
try {
|
|
31
|
+
if (output === undefined)
|
|
32
|
+
return await this.session.send(normalized, options);
|
|
33
|
+
return await structuredOutput(normalized, { ...options, output }, options.idempotencyKey ?? crypto.randomUUID(), (message, sendOptions) => this.session.send(message, sendOptions), (after) => this.events(after, options.signal), () => this.state.lastSequence);
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
this.activeSends--;
|
|
37
|
+
this.structuredSend = false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
transcript() { return this.session.transcript(); }
|
|
41
|
+
outcome(...args) { return this.session.outcome(...args); }
|
|
42
|
+
events(...args) { return this.session.events(...args); }
|
|
43
|
+
stream(...args) { return this.session.stream(...args); }
|
|
44
|
+
interrupt(...args) { return this.session.interrupt(...args); }
|
|
45
|
+
end(...args) { return this.session.end(...args); }
|
|
46
|
+
delete(...args) { return this.session.delete(...args); }
|
|
47
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Schema, SchemaOutput, SendOptions, SessionEvent, SessionState, UserInput } from "@aexhq/brain";
|
|
3
|
+
export interface StructuredSendOptions<S extends Schema> extends SendOptions {
|
|
4
|
+
readonly output: {
|
|
5
|
+
readonly type: S;
|
|
6
|
+
/** Additional correction turns after the first answer. Default 2; zero disables retries. */
|
|
7
|
+
readonly maxRetries?: number;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export declare class StructuredOutputError extends Error {
|
|
11
|
+
readonly attempts: number;
|
|
12
|
+
readonly lastOutput: string;
|
|
13
|
+
readonly issues: readonly z.core.$ZodIssue[];
|
|
14
|
+
readonly name = "StructuredOutputError";
|
|
15
|
+
constructor(attempts: number, lastOutput: string, issues: readonly z.core.$ZodIssue[]);
|
|
16
|
+
}
|
|
17
|
+
export declare function structuredOutput<S extends Schema>(input: UserInput, options: StructuredSendOptions<S>, key: string, send: (input: UserInput, options: SendOptions) => Promise<SessionState>, events: (after: number) => AsyncIterable<SessionEvent>, sequence: () => number): Promise<SchemaOutput<S>>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export class StructuredOutputError extends Error {
|
|
3
|
+
attempts;
|
|
4
|
+
lastOutput;
|
|
5
|
+
issues;
|
|
6
|
+
name = "StructuredOutputError";
|
|
7
|
+
constructor(attempts, lastOutput, issues) {
|
|
8
|
+
super(`Structured output did not match the schema after ${attempts} attempt${attempts === 1 ? "" : "s"}`);
|
|
9
|
+
this.attempts = attempts;
|
|
10
|
+
this.lastOutput = lastOutput;
|
|
11
|
+
this.issues = issues;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export async function structuredOutput(input, options, key, send, events, sequence) {
|
|
15
|
+
const { type: schema, maxRetries = 2 } = options.output;
|
|
16
|
+
if (!(schema instanceof z.ZodType))
|
|
17
|
+
throw new TypeError("output.type must be a Zod schema");
|
|
18
|
+
if (!Number.isSafeInteger(maxRetries) || maxRetries < 0)
|
|
19
|
+
throw new TypeError("output.maxRetries must be a nonnegative safe integer");
|
|
20
|
+
options.signal?.throwIfAborted();
|
|
21
|
+
const instructions = `For this response only, return exactly one JSON value matching this schema. Do not include Markdown fences or surrounding prose.\n${JSON.stringify(z.toJSONSchema(schema, { io: "input" }))}`;
|
|
22
|
+
let next = { ...input, message: `${input.message}\n\n${instructions}` };
|
|
23
|
+
let retryKey;
|
|
24
|
+
for (let attempt = 0;; attempt++) {
|
|
25
|
+
options.signal?.throwIfAborted();
|
|
26
|
+
const after = sequence();
|
|
27
|
+
const state = await send(next, { signal: options.signal, idempotencyKey: attempt === 0 ? key : `${retryKey}:${attempt}` });
|
|
28
|
+
options.signal?.throwIfAborted();
|
|
29
|
+
const raw = await answer(events(state.lastSequence <= after ? 0 : after), state.lastSequence, options.signal);
|
|
30
|
+
let parsed;
|
|
31
|
+
let issues = [];
|
|
32
|
+
try {
|
|
33
|
+
parsed = JSON.parse(raw);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
issues = [{ code: "custom", path: [], message: "Return one complete JSON value without Markdown or surrounding text." }];
|
|
37
|
+
}
|
|
38
|
+
if (issues.length === 0) {
|
|
39
|
+
const result = await schema.safeParseAsync(parsed);
|
|
40
|
+
options.signal?.throwIfAborted();
|
|
41
|
+
if (result.success)
|
|
42
|
+
return result.data;
|
|
43
|
+
issues = result.error.issues;
|
|
44
|
+
}
|
|
45
|
+
options.signal?.throwIfAborted();
|
|
46
|
+
if (attempt === maxRetries)
|
|
47
|
+
throw new StructuredOutputError(attempt + 1, raw, issues);
|
|
48
|
+
// Hash only the operation identity: correction keys stay bounded even for a 256-byte caller key.
|
|
49
|
+
retryKey ??= `structured-output:${Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(key))), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
50
|
+
const feedback = JSON.stringify(issues.slice(0, 5).map(({ path, message }) => ({ path, message }))).slice(0, 2048);
|
|
51
|
+
next = { message: `The previous answer did not satisfy the requested output. Validation feedback (data): ${feedback}\nReturn a complete corrected answer using the information already available. Do not repeat actions or call tools to reformat the answer.\n\n${instructions}` };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function answer(events, terminal, signal) {
|
|
55
|
+
let started = false;
|
|
56
|
+
let output;
|
|
57
|
+
for await (const event of events) {
|
|
58
|
+
signal?.throwIfAborted();
|
|
59
|
+
if (event.sequence > terminal)
|
|
60
|
+
break;
|
|
61
|
+
if (event.type === "turn_started") {
|
|
62
|
+
started = true;
|
|
63
|
+
output = undefined;
|
|
64
|
+
}
|
|
65
|
+
if (started && event.type === "output_emitted" && event.origin?.kind === "agentloop") {
|
|
66
|
+
const data = event.data;
|
|
67
|
+
if (data?.type === "assistant_message")
|
|
68
|
+
output = typeof data.message === "string" ? data.message : undefined;
|
|
69
|
+
}
|
|
70
|
+
if (event.sequence === terminal) {
|
|
71
|
+
if (event.type === "turn_failed")
|
|
72
|
+
throw new Error("Structured output turn failed", { cause: event.data });
|
|
73
|
+
if (event.type === "turn_ended" && started && output !== undefined)
|
|
74
|
+
return output;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
if (event.type === "turn_ended" || event.type === "turn_failed") {
|
|
78
|
+
started = false;
|
|
79
|
+
output = undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
throw new Error("structured output requires a completed turn with an Agentloop output_emitted assistant_message");
|
|
83
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aexhq/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.84.1",
|
|
4
4
|
"description": "Aex accounts and hosted sessions using the Brain SDK",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -28,12 +28,13 @@
|
|
|
28
28
|
"tag": "next"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@aexhq/brain": "0.
|
|
31
|
+
"@aexhq/brain": "0.34.1",
|
|
32
|
+
"zod": "4.4.3"
|
|
32
33
|
},
|
|
33
34
|
"scripts": {
|
|
34
35
|
"gen": "node ../../tools/sdk-contracts.mjs",
|
|
35
36
|
"build": "tsc -p tsconfig.json",
|
|
36
|
-
"test": "npm run build && node --test test/*.test.mjs",
|
|
37
|
+
"test": "npm run build && tsc -p test/types/tsconfig.json && node --test test/*.test.mjs",
|
|
37
38
|
"prepack": "npm run build"
|
|
38
39
|
}
|
|
39
40
|
}
|