@jxsuite/ai 0.36.4 → 0.36.5
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 +35 -35
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
The provider-agnostic substrate Jx Studio's assistant is built on: a streaming LLM client
|
|
4
4
|
abstraction that normalizes provider SSE into six event shapes, a tool registry that speaks OpenAI
|
|
5
|
-
function-calling, and a reactive chat store. It has no Jx-domain dependencies
|
|
6
|
-
import of `ProblemDetails` from `@jxsuite/protocol
|
|
5
|
+
function-calling, and a reactive chat store. It has no Jx-domain dependencies (just a type-only
|
|
6
|
+
import of `ProblemDetails` from `@jxsuite/protocol`, plus `@vue/reactivity`), and its only platform
|
|
7
7
|
APIs are `fetch`, `TextDecoder` and `AbortSignal`, so the same modules run in the browser and in
|
|
8
8
|
Bun/Node.
|
|
9
9
|
|
|
@@ -17,8 +17,8 @@ documentation for the Studio feature lives at [`docs/studio/ai.md`](../../docs/s
|
|
|
17
17
|
| `@jxsuite/ai/tools` | `createToolDefinition`, `createToolRegistry`, `toolSuccess`, `toolError`, and their types |
|
|
18
18
|
| `@jxsuite/ai/chat-state` | `createChatState`, `ChatStore`, `Message`, `ToolCallRecord`, `ChatState`, `MessageRole` |
|
|
19
19
|
|
|
20
|
-
`toolSuccess` / `toolError` are **not** re-exported by the barrel
|
|
21
|
-
`@jxsuite/ai/tools`. Importing the subpaths
|
|
20
|
+
`toolSuccess` / `toolError` are **not** re-exported by the barrel. Import them from
|
|
21
|
+
`@jxsuite/ai/tools`. Importing the subpaths instead of the root is also what makes the package
|
|
22
22
|
tree-shakeable.
|
|
23
23
|
|
|
24
24
|
## The streaming client
|
|
@@ -30,13 +30,13 @@ streamChat(messages: object[], tools: object[], systemPrompt: string, signal: Ab
|
|
|
30
30
|
: AsyncGenerator<StreamEvent>;
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
Every implementation yields the same discriminated union
|
|
34
|
-
`tool_call_start {id, name}`, `tool_call_delta {id, args}`,
|
|
35
|
-
`done {stopReason}`, `error {message, code?, problem?}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
Every implementation yields the same discriminated union, whose type strings are also published as
|
|
34
|
+
`STREAM_EVENT_TYPES`: `delta {content}`, `tool_call_start {id, name}`, `tool_call_delta {id, args}`,
|
|
35
|
+
`tool_call_end {id}`, `done {stopReason}`, `error {message, code?, problem?}`. This union is the
|
|
36
|
+
contract a third-party Studio backend must satisfy for the `ai/chat` route (see
|
|
37
|
+
[`packages/protocol/README.md`](../protocol/README.md)); `@jxsuite/server` emits the same format
|
|
38
|
+
without depending on this package. `StreamEvent` and each member are exported, so an implementer can
|
|
39
|
+
import the type instead of reconstructing it.
|
|
40
40
|
|
|
41
41
|
```ts
|
|
42
42
|
import { createProxyStreamingClient } from "@jxsuite/ai";
|
|
@@ -57,7 +57,7 @@ for await (const event of client.streamChat(messages, tools, systemPrompt, signa
|
|
|
57
57
|
`stream_options: { include_usage: true }`, and `{ role: "system", content: systemPrompt }`
|
|
58
58
|
prepended to your messages. A non-empty `tools` array also sets `tool_choice: "auto"` and
|
|
59
59
|
`parallel_tool_calls: true`; an empty one sends no `tools` key. `temperature` is forwarded **only
|
|
60
|
-
when defined**, because reasoning models (GPT-5.x, o-series) reject a custom temperature
|
|
60
|
+
when defined**, because reasoning models (GPT-5.x, o-series) reject a custom temperature. That is a
|
|
61
61
|
provider behaviour recorded in the source comment, not covered by a test here. It does
|
|
62
62
|
its own SSE framing and reassembles fragmented tool-call argument JSON keyed by the provider's
|
|
63
63
|
`delta.tool_calls[].index`.
|
|
@@ -66,7 +66,7 @@ for await (const event of client.streamChat(messages, tools, systemPrompt, signa
|
|
|
66
66
|
ships (`specs/ai.md` §2).
|
|
67
67
|
- **`createProxyStreamingClient({ chatUrl, model = "gpt-4o", apiKey, baseUrl })`** POSTs
|
|
68
68
|
`{ messages, tools, systemPrompt, model }` to an endpoint that already speaks the normalized
|
|
69
|
-
format
|
|
69
|
+
format (the dev/desktop server's `/__studio/ai/chat`). It handles no credentials of its own;
|
|
70
70
|
`apiKey` and `baseUrl` are passed through as the `X-Api-Key` and `X-Api-Base-URL` headers only
|
|
71
71
|
when truthy, and the proxy owns the provider key. It is a pass-through, **not** a translator: it
|
|
72
72
|
re-yields whatever parses out of each `data:` line and returns at the first `done` or `error`, so
|
|
@@ -85,8 +85,8 @@ Three behaviours are easy to get wrong when consuming the stream:
|
|
|
85
85
|
`done` frame ends its generator with no `done` at all.
|
|
86
86
|
3. **`error` frames may carry an RFC 9457 `problem` beside the human `message`,** because the
|
|
87
87
|
response has already begun with a 200 and the status can no longer change (a committed standards
|
|
88
|
-
row in `specs/ai.md` §5). The field is optional and no client in this package sets it
|
|
89
|
-
`@jxsuite/server`'s proxy is what populates it and `createProxyStreamingClient` passes it
|
|
88
|
+
row in `specs/ai.md` §5). The field is optional and no client in this package sets it.
|
|
89
|
+
`@jxsuite/server`'s proxy is what populates it, and `createProxyStreamingClient` passes it
|
|
90
90
|
through. Read `problem.type` for machine dispatch when it is present; keep showing `message`.
|
|
91
91
|
|
|
92
92
|
## The tool registry
|
|
@@ -117,38 +117,38 @@ registry.register(
|
|
|
117
117
|
registry's own argument validation. `llmStrict` (default `false`) is the only one that reaches the
|
|
118
118
|
wire, adding OpenAI's `strict: true` to the emitted function schema. OpenAI strict mode demands
|
|
119
119
|
`additionalProperties: false` and every property in `required`, which Jx's own tools deliberately
|
|
120
|
-
violate
|
|
120
|
+
violate, so GPT-5.x rejects such a request outright (again, committed rationale and no test).
|
|
121
121
|
- **`validate()` is a lightweight structural check, not a JSON Schema validator.** It reads only
|
|
122
122
|
`type`, `properties` and `required`: nested schemas, `enum`, `pattern`, `minimum` and every other
|
|
123
123
|
keyword are ignored. Unknown properties are ignored too (the model may send extra); a required key
|
|
124
124
|
that is present but `null`/`undefined` counts as missing; numeric strings coerce, so `"42"`
|
|
125
125
|
validates against `number`. `strict: false`, or a schema without `properties`, skips it entirely.
|
|
126
126
|
- **`execute()` never throws.** An unknown tool, a validation failure, and an exception inside the
|
|
127
|
-
tool all return `{ success: false, error }
|
|
127
|
+
tool all return `{ success: false, error }`, which is what lets an agent loop feed the failure
|
|
128
128
|
back to the model and self-correct. It calls `this.validate(...)`, so it is `this`-bound: do not
|
|
129
129
|
destructure it off the registry.
|
|
130
130
|
- Re-registering a name `console.warn`s and overwrites.
|
|
131
131
|
|
|
132
|
-
To vary the tool set per turn, wrap rather than mutate
|
|
132
|
+
To vary the tool set per turn, wrap rather than mutate. Studio's `createGatedToolRegistry` returns
|
|
133
133
|
a `ToolRegistry` that filters `list()`/`listForLLM()` through per-tool predicates
|
|
134
134
|
(`packages/studio/src/services/gated-registry.ts`).
|
|
135
135
|
|
|
136
136
|
## The reactive chat store
|
|
137
137
|
|
|
138
|
-
`createChatState(options?: { model?: string })` returns a `@vue/reactivity` `reactive()` store
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
the default too. `status` is `"idle" | "streaming" | "error"`; `toMessagesArray()`
|
|
143
|
-
|
|
144
|
-
|
|
138
|
+
`createChatState(options?: { model?: string })` returns a `@vue/reactivity` `reactive()` store with
|
|
139
|
+
its mutators `Object.assign`ed onto the same proxy, so the returned value is both the state and the
|
|
140
|
+
API. The store holds `messages`, `status`, `streamingContent`, `pendingToolCalls`, `error`, `model`,
|
|
141
|
+
`tokenCount` and `contextWarning`. The model falls back to `"gpt-4o"` via `||`, so an empty string
|
|
142
|
+
takes the default too. `status` is `"idle" | "streaming" | "error"`; `toMessagesArray()` serializes
|
|
143
|
+
back to OpenAI wire shape (assistant `tool_calls`, `tool` messages with `tool_call_id`, everything
|
|
144
|
+
else `{ role, content }`).
|
|
145
145
|
|
|
146
146
|
Two ordering rules inside `beginAssistantTurn` are load-bearing, and any reimplementation must keep
|
|
147
147
|
them: `status` is set to `"streaming"` **before** the placeholder message is pushed (the push itself
|
|
148
148
|
triggers effects, and a reader seeing `"idle"` renders the empty placeholder as a finished message),
|
|
149
|
-
and the pushed object is immediately re-read back through the proxy (`store.messages.at(-1)`)
|
|
150
|
-
mutating the raw object would notify nothing. A regression test asserts that an `effect()`
|
|
151
|
-
`messages.at(-1).content` re-runs on every `appendDelta`.
|
|
149
|
+
and the pushed object is immediately re-read back through the proxy (`store.messages.at(-1)`),
|
|
150
|
+
because mutating the raw object would notify nothing. A regression test asserts that an `effect()`
|
|
151
|
+
reading `messages.at(-1).content` re-runs on every `appendDelta`.
|
|
152
152
|
|
|
153
153
|
Further surprises:
|
|
154
154
|
|
|
@@ -168,10 +168,10 @@ Further surprises:
|
|
|
168
168
|
|
|
169
169
|
## Versioning
|
|
170
170
|
|
|
171
|
-
Published to npm as `@jxsuite/ai
|
|
172
|
-
|
|
173
|
-
consumer must be
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
171
|
+
Published to npm as `@jxsuite/ai`, following the monorepo's release train. Like every published
|
|
172
|
+
`@jxsuite` library, it ships TypeScript source: every `exports` subpath resolves to `./src/*.ts`, so
|
|
173
|
+
a consumer must be able to compile TypeScript out of `node_modules`. Within the monorepo,
|
|
174
|
+
`@jxsuite/studio` depends on it via `workspace:^` and is its only consumer. `@vue/reactivity` is
|
|
175
|
+
pinned to an **exact** version, matching `@jxsuite/studio`, `@jxsuite/runtime` and
|
|
176
|
+
`@jxsuite/compiler`: reactive proxies from one copy do not track in effects from another, and the
|
|
177
|
+
failure is silent.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/ai",
|
|
3
|
-
"version": "0.36.
|
|
3
|
+
"version": "0.36.5",
|
|
4
4
|
"description": "AI infrastructure for Jx Suite — streaming LLM client abstraction, tool registry, and reactive chat state",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build": "bun build ./src/index.ts --outdir dist --target browser --sourcemap=linked",
|
|
27
27
|
"test": "bun test",
|
|
28
|
-
"upgrade": "
|
|
28
|
+
"upgrade": "bun update --latest && bun install"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@jxsuite/protocol": "^2.
|
|
31
|
+
"@jxsuite/protocol": "^2.1.0",
|
|
32
32
|
"@vue/reactivity": "3.5.41"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@happy-dom/global-registrator": "^20.11.
|
|
35
|
+
"@happy-dom/global-registrator": "^20.11.8"
|
|
36
36
|
}
|
|
37
37
|
}
|