@particle-academy/prism 0.0.0 → 0.1.0
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 +21 -21
- package/README.md +155 -155
- package/package.json +2 -2
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024 TJ MIller
|
|
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.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 TJ MIller
|
|
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,155 +1,155 @@
|
|
|
1
|
-
# @particle-academy/prism
|
|
2
|
-
|
|
3
|
-
A TypeScript port of [Prism](https://github.com/Particle-Academy/prism), the
|
|
4
|
-
PHP/Laravel unified API layer over LLM providers.
|
|
5
|
-
|
|
6
|
-
Real TypeScript: promises, discriminated unions, strict null checking. What it
|
|
7
|
-
is *not* is a transliteration — the idioms are this language's. What crosses the
|
|
8
|
-
wire, and what comes back, is identical.
|
|
9
|
-
|
|
10
|
-
> **Working on this package?** Read **[`AGENTS.md`](AGENTS.md)** first — the boundary
|
|
11
|
-
> this package has to hold, the gates that must be green, and the traps that have
|
|
12
|
-
> already caught someone.
|
|
13
|
-
> `@link AGENTS.md`
|
|
14
|
-
|
|
15
|
-
## Scope
|
|
16
|
-
|
|
17
|
-
**Every capability the reference has**, and three of its eighteen providers.
|
|
18
|
-
That is the shape of the remaining drift: one axis, not two.
|
|
19
|
-
|
|
20
|
-
- **Capabilities — all of them.** `text`, `structured`, `stream`, `embeddings`,
|
|
21
|
-
`images`, `moderation`, `audio` (`textToSpeech` and `speechToText`), `files`,
|
|
22
|
-
`batch`, `fim`. A user message carries images and documents, and each provider
|
|
23
|
-
spells them its own way.
|
|
24
|
-
- **Providers — `anthropic`, `mistral`, `openai`.** The OpenAI provider speaks
|
|
25
|
-
the **Responses API**; Mistral brings the chat-completions shape, which is a
|
|
26
|
-
different wire format rather than a variant of it.
|
|
27
|
-
|
|
28
|
-
Not included, and not stubbed: **the tool-execution loop** — a response that
|
|
29
|
-
finishes on tool calls is refused with `tool_loop_not_supported` rather than
|
|
30
|
-
half-executed — and the fifteen providers the reference has and this port does
|
|
31
|
-
not. Asking for one fails with the `unsupported_provider_action` code rather
|
|
32
|
-
than returning something plausible.
|
|
33
|
-
|
|
34
|
-
Not every capability reaches every provider, because not every provider has one.
|
|
35
|
-
`images`, `moderation`, `textToSpeech`, `files` and `batch` are OpenAI-only
|
|
36
|
-
here, and `fim` is Mistral-only because Mistral is the only provider that
|
|
37
|
-
implements it at all — in the reference as well. See the envelope's port gaps
|
|
38
|
-
register for which of those are work someone could do and which are facts about
|
|
39
|
-
the provider.
|
|
40
|
-
|
|
41
|
-
Every value object serialises **and rebuilds** — `toObject()` and a static
|
|
42
|
-
`fromObject()`. The PHP package has only the first half, which left each consumer
|
|
43
|
-
to invent the second; one of those inventions shipped a defect that corrupted
|
|
44
|
-
stored assistant messages. Both halves ship here, and the rebuild direction is
|
|
45
|
-
pinned by fixtures.
|
|
46
|
-
|
|
47
|
-
Failures carry a stable `code` (`PrismError.code`). The prose is not part of the
|
|
48
|
-
contract and will change; the code will not.
|
|
49
|
-
|
|
50
|
-
## Install
|
|
51
|
-
|
|
52
|
-
```sh
|
|
53
|
-
npm install @particle-academy/prism
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Node 20 or newer.
|
|
57
|
-
|
|
58
|
-
## Usage
|
|
59
|
-
|
|
60
|
-
```ts
|
|
61
|
-
import { Prism } from '@particle-academy/prism';
|
|
62
|
-
|
|
63
|
-
const response = await Prism.text()
|
|
64
|
-
.using('openai', 'gpt-4o')
|
|
65
|
-
.withSystemPrompt('You are terse.')
|
|
66
|
-
.withPrompt('Who are you?')
|
|
67
|
-
.asText();
|
|
68
|
-
|
|
69
|
-
console.log(response.text);
|
|
70
|
-
console.log(response.usage.promptTokens, response.usage.completionTokens);
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Configuration is explicit first, environment second — `OPENAI_API_KEY`,
|
|
74
|
-
`OPENAI_URL`, `OPENAI_ORGANIZATION`, `OPENAI_PROJECT`:
|
|
75
|
-
|
|
76
|
-
```ts
|
|
77
|
-
Prism.text().using('openai', 'gpt-4o', { apiKey: 'sk-…', transport });
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
`transport` is the seam between the provider and the network. Pass one and no
|
|
81
|
-
test ever touches a socket:
|
|
82
|
-
|
|
83
|
-
```ts
|
|
84
|
-
import type { HttpTransport } from '@particle-academy/prism';
|
|
85
|
-
|
|
86
|
-
const transport: HttpTransport = async (request) => ({
|
|
87
|
-
status: 200,
|
|
88
|
-
headers: {},
|
|
89
|
-
body: recordedPayload,
|
|
90
|
-
rawBody: JSON.stringify(recordedPayload),
|
|
91
|
-
});
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
Build a request without sending it, or parse a stored payload without HTTP:
|
|
95
|
-
|
|
96
|
-
```ts
|
|
97
|
-
import { buildRequestBody, canonicalJson, parseTextResponse } from '@particle-academy/prism';
|
|
98
|
-
|
|
99
|
-
const request = Prism.text().using('openai', 'gpt-4o').withPrompt('hi').toRequest();
|
|
100
|
-
|
|
101
|
-
canonicalJson(buildRequestBody(request)); // exactly the bytes that would be POSTed
|
|
102
|
-
parseTextResponse(request, recordedPayload); // the same parse a live call performs
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
## Parity
|
|
106
|
-
|
|
107
|
-
This port's behaviour is not defined by its own tests. It is pinned by
|
|
108
|
-
[**prism-parity**](https://github.com/Particle-Academy/prism-parity) — a
|
|
109
|
-
language-independent conformance corpus generated by executing the PHP
|
|
110
|
-
reference, which states the rules once so three implementations cannot each
|
|
111
|
-
drift into their own reading of them.
|
|
112
|
-
|
|
113
|
-
The corpus and its loader install as `@particle-academy/prism-conformance`. The
|
|
114
|
-
runner lives in [`conformance/runner.mjs`](conformance/runner.mjs) and implements
|
|
115
|
-
prism-parity's subprocess contract, so `scripts/cross-check.mjs` can run this
|
|
116
|
-
port and the others side by side and require **identical verdicts** — a stronger
|
|
117
|
-
claim than three suites that each went green alone.
|
|
118
|
-
|
|
119
|
-
```sh
|
|
120
|
-
git clone https://github.com/Particle-Academy/prism-parity .parity
|
|
121
|
-
npm install ./.parity/loaders/ts
|
|
122
|
-
npm run conformance # every suite
|
|
123
|
-
node conformance/runner.mjs --suite openai-text-request
|
|
124
|
-
node conformance/runner.mjs --probe omit-null-keys
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
Two rows diverge from the reference on purpose, and the corpus says which and
|
|
128
|
-
why: `trq-0021` and `trq-0022`, where the reference drops a prompt of `""` or
|
|
129
|
-
`"0"` and this port sends it. Read the corpus for the rest — restating its rules
|
|
130
|
-
here would only give them somewhere to drift.
|
|
131
|
-
|
|
132
|
-
### Probes
|
|
133
|
-
|
|
134
|
-
`--probe` runs the port with one deliberate defect injected, from the corpus's
|
|
135
|
-
`probes/probes.json`. Each probe declares the exact set of case ids it must fail,
|
|
136
|
-
and [`tests/probes.test.ts`](tests/probes.test.ts) asserts the observed set
|
|
137
|
-
*equals* it — not that it is non-empty. A table every plausible implementation
|
|
138
|
-
passes measures nothing.
|
|
139
|
-
|
|
140
|
-
The mutants live in [`conformance/mutations.mjs`](conformance/mutations.mjs),
|
|
141
|
-
outside `src/`: a defect you can switch on in production is a defect.
|
|
142
|
-
|
|
143
|
-
## Development
|
|
144
|
-
|
|
145
|
-
```sh
|
|
146
|
-
npm run build # tsc → dist/
|
|
147
|
-
npm run typecheck
|
|
148
|
-
npm run lint
|
|
149
|
-
npm test
|
|
150
|
-
npm run conformance
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
## License
|
|
154
|
-
|
|
155
|
-
MIT. See [LICENSE](LICENSE).
|
|
1
|
+
# @particle-academy/prism
|
|
2
|
+
|
|
3
|
+
A TypeScript port of [Prism](https://github.com/Particle-Academy/prism), the
|
|
4
|
+
PHP/Laravel unified API layer over LLM providers.
|
|
5
|
+
|
|
6
|
+
Real TypeScript: promises, discriminated unions, strict null checking. What it
|
|
7
|
+
is *not* is a transliteration — the idioms are this language's. What crosses the
|
|
8
|
+
wire, and what comes back, is identical.
|
|
9
|
+
|
|
10
|
+
> **Working on this package?** Read **[`AGENTS.md`](AGENTS.md)** first — the boundary
|
|
11
|
+
> this package has to hold, the gates that must be green, and the traps that have
|
|
12
|
+
> already caught someone.
|
|
13
|
+
> `@link AGENTS.md`
|
|
14
|
+
|
|
15
|
+
## Scope
|
|
16
|
+
|
|
17
|
+
**Every capability the reference has**, and three of its eighteen providers.
|
|
18
|
+
That is the shape of the remaining drift: one axis, not two.
|
|
19
|
+
|
|
20
|
+
- **Capabilities — all of them.** `text`, `structured`, `stream`, `embeddings`,
|
|
21
|
+
`images`, `moderation`, `audio` (`textToSpeech` and `speechToText`), `files`,
|
|
22
|
+
`batch`, `fim`. A user message carries images and documents, and each provider
|
|
23
|
+
spells them its own way.
|
|
24
|
+
- **Providers — `anthropic`, `mistral`, `openai`.** The OpenAI provider speaks
|
|
25
|
+
the **Responses API**; Mistral brings the chat-completions shape, which is a
|
|
26
|
+
different wire format rather than a variant of it.
|
|
27
|
+
|
|
28
|
+
Not included, and not stubbed: **the tool-execution loop** — a response that
|
|
29
|
+
finishes on tool calls is refused with `tool_loop_not_supported` rather than
|
|
30
|
+
half-executed — and the fifteen providers the reference has and this port does
|
|
31
|
+
not. Asking for one fails with the `unsupported_provider_action` code rather
|
|
32
|
+
than returning something plausible.
|
|
33
|
+
|
|
34
|
+
Not every capability reaches every provider, because not every provider has one.
|
|
35
|
+
`images`, `moderation`, `textToSpeech`, `files` and `batch` are OpenAI-only
|
|
36
|
+
here, and `fim` is Mistral-only because Mistral is the only provider that
|
|
37
|
+
implements it at all — in the reference as well. See the envelope's port gaps
|
|
38
|
+
register for which of those are work someone could do and which are facts about
|
|
39
|
+
the provider.
|
|
40
|
+
|
|
41
|
+
Every value object serialises **and rebuilds** — `toObject()` and a static
|
|
42
|
+
`fromObject()`. The PHP package has only the first half, which left each consumer
|
|
43
|
+
to invent the second; one of those inventions shipped a defect that corrupted
|
|
44
|
+
stored assistant messages. Both halves ship here, and the rebuild direction is
|
|
45
|
+
pinned by fixtures.
|
|
46
|
+
|
|
47
|
+
Failures carry a stable `code` (`PrismError.code`). The prose is not part of the
|
|
48
|
+
contract and will change; the code will not.
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
npm install @particle-academy/prism
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Node 20 or newer.
|
|
57
|
+
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { Prism } from '@particle-academy/prism';
|
|
62
|
+
|
|
63
|
+
const response = await Prism.text()
|
|
64
|
+
.using('openai', 'gpt-4o')
|
|
65
|
+
.withSystemPrompt('You are terse.')
|
|
66
|
+
.withPrompt('Who are you?')
|
|
67
|
+
.asText();
|
|
68
|
+
|
|
69
|
+
console.log(response.text);
|
|
70
|
+
console.log(response.usage.promptTokens, response.usage.completionTokens);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Configuration is explicit first, environment second — `OPENAI_API_KEY`,
|
|
74
|
+
`OPENAI_URL`, `OPENAI_ORGANIZATION`, `OPENAI_PROJECT`:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
Prism.text().using('openai', 'gpt-4o', { apiKey: 'sk-…', transport });
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`transport` is the seam between the provider and the network. Pass one and no
|
|
81
|
+
test ever touches a socket:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import type { HttpTransport } from '@particle-academy/prism';
|
|
85
|
+
|
|
86
|
+
const transport: HttpTransport = async (request) => ({
|
|
87
|
+
status: 200,
|
|
88
|
+
headers: {},
|
|
89
|
+
body: recordedPayload,
|
|
90
|
+
rawBody: JSON.stringify(recordedPayload),
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Build a request without sending it, or parse a stored payload without HTTP:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { buildRequestBody, canonicalJson, parseTextResponse } from '@particle-academy/prism';
|
|
98
|
+
|
|
99
|
+
const request = Prism.text().using('openai', 'gpt-4o').withPrompt('hi').toRequest();
|
|
100
|
+
|
|
101
|
+
canonicalJson(buildRequestBody(request)); // exactly the bytes that would be POSTed
|
|
102
|
+
parseTextResponse(request, recordedPayload); // the same parse a live call performs
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Parity
|
|
106
|
+
|
|
107
|
+
This port's behaviour is not defined by its own tests. It is pinned by
|
|
108
|
+
[**prism-parity**](https://github.com/Particle-Academy/prism-parity) — a
|
|
109
|
+
language-independent conformance corpus generated by executing the PHP
|
|
110
|
+
reference, which states the rules once so three implementations cannot each
|
|
111
|
+
drift into their own reading of them.
|
|
112
|
+
|
|
113
|
+
The corpus and its loader install as `@particle-academy/prism-conformance`. The
|
|
114
|
+
runner lives in [`conformance/runner.mjs`](conformance/runner.mjs) and implements
|
|
115
|
+
prism-parity's subprocess contract, so `scripts/cross-check.mjs` can run this
|
|
116
|
+
port and the others side by side and require **identical verdicts** — a stronger
|
|
117
|
+
claim than three suites that each went green alone.
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
git clone https://github.com/Particle-Academy/prism-parity .parity
|
|
121
|
+
npm install ./.parity/loaders/ts
|
|
122
|
+
npm run conformance # every suite
|
|
123
|
+
node conformance/runner.mjs --suite openai-text-request
|
|
124
|
+
node conformance/runner.mjs --probe omit-null-keys
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Two rows diverge from the reference on purpose, and the corpus says which and
|
|
128
|
+
why: `trq-0021` and `trq-0022`, where the reference drops a prompt of `""` or
|
|
129
|
+
`"0"` and this port sends it. Read the corpus for the rest — restating its rules
|
|
130
|
+
here would only give them somewhere to drift.
|
|
131
|
+
|
|
132
|
+
### Probes
|
|
133
|
+
|
|
134
|
+
`--probe` runs the port with one deliberate defect injected, from the corpus's
|
|
135
|
+
`probes/probes.json`. Each probe declares the exact set of case ids it must fail,
|
|
136
|
+
and [`tests/probes.test.ts`](tests/probes.test.ts) asserts the observed set
|
|
137
|
+
*equals* it — not that it is non-empty. A table every plausible implementation
|
|
138
|
+
passes measures nothing.
|
|
139
|
+
|
|
140
|
+
The mutants live in [`conformance/mutations.mjs`](conformance/mutations.mjs),
|
|
141
|
+
outside `src/`: a defect you can switch on in production is a defect.
|
|
142
|
+
|
|
143
|
+
## Development
|
|
144
|
+
|
|
145
|
+
```sh
|
|
146
|
+
npm run build # tsc → dist/
|
|
147
|
+
npm run typecheck
|
|
148
|
+
npm run lint
|
|
149
|
+
npm test
|
|
150
|
+
npm run conformance
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT. See [LICENSE](LICENSE).
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@particle-academy/prism",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "TypeScript port of Prism
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript port of Prism \u2014 a unified API layer over LLM providers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|