@assembly-js/app-foundation 0.6.3 → 0.6.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/CHANGELOG.md +7 -0
- package/CLAUDE.md +3 -1
- package/dist/mint-bearer.js +7 -1
- package/dist/pdf.d.ts +5 -1
- package/dist/pdf.js +7 -3
- package/dist/scripts.js +3 -1
- package/package.json +1 -1
- package/skills/assembly-ai/SKILL.md +14 -6
- package/skills/assembly-storage/SKILL.md +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -19,6 +19,13 @@ A patch release never changes what an app's build requires; a minor release may
|
|
|
19
19
|
not by hand; see `changes/README.md`. CI fails a PR that changes a check script without a
|
|
20
20
|
`bump: minor` change file.
|
|
21
21
|
|
|
22
|
+
## 0.6.5
|
|
23
|
+
- [guidance] The `assembly-storage` skill says the `assembly` option of `detectPdfFields` is only an opt-in flag. The `assembly-ai` skill and the `assembly.fetch()` prose now call the AI proxy on the installer client (`getInstallerAssembly()`, gated by `await getAssembly()` first) instead of the viewer's client.
|
|
24
|
+
- [behavior] `detectPdfFields` now runs its AI step on the installer client, and degrades to a warning when there is no verified viewer. `options.assembly` is deprecated and ignored; passing it still opts in to the AI step.
|
|
25
|
+
|
|
26
|
+
## 0.6.4
|
|
27
|
+
- [behavior] An app's sandbox now sends its sandbox grant when minting an app bearer, so the platform can tell its calls from the deployed app's.
|
|
28
|
+
|
|
22
29
|
## 0.6.3
|
|
23
30
|
- [fix] The client-call scan no longer reports `retrieveCompany` on the viewer client as breaking: the platform now lets a client read its own active company (`GET /v1/companies/{id}`), and refuses any other company. Nothing in the app changes.
|
|
24
31
|
|
package/CLAUDE.md
CHANGED
|
@@ -1260,9 +1260,11 @@ Convention-breakers to know up front:
|
|
|
1260
1260
|
|
|
1261
1261
|
### `assembly.fetch()` — for non-SDK endpoints
|
|
1262
1262
|
|
|
1263
|
-
The SDK exposes a generic `fetch(path, init?)` method for calling Assembly API endpoints that aren't in the generated SDK methods (e.g. the AI proxy). It prepends the base URL and injects auth headers (`Authorization: Bearer` from the
|
|
1263
|
+
The SDK exposes a generic `fetch(path, init?)` method for calling Assembly API endpoints that aren't in the generated SDK methods (e.g. the AI proxy). It prepends the base URL and injects auth headers (`Authorization: Bearer` from the client you call it on, plus `X-Assembly-SDK-Version`) automatically — never `X-API-Key` on data calls — returning a standard `Response`. The AI proxy is app-owned, so call it on the installer client (`getInstallerAssembly()`, server-side only), gated by `await getAssembly()` first because that throws without a verified viewer:
|
|
1264
1264
|
|
|
1265
1265
|
```ts
|
|
1266
|
+
await getAssembly(); // the gate: throws without a verified viewer
|
|
1267
|
+
const assembly = await getInstallerAssembly();
|
|
1266
1268
|
const response = await assembly.fetch('/v1/ai/openai/chat/completions', {
|
|
1267
1269
|
method: 'POST',
|
|
1268
1270
|
body: JSON.stringify({ model: 'gpt-4o-mini', messages }),
|
package/dist/mint-bearer.js
CHANGED
|
@@ -171,9 +171,15 @@ export async function mintBearerFromAppKey() {
|
|
|
171
171
|
const controller = new AbortController();
|
|
172
172
|
const timeoutId = setTimeout(() => controller.abort(), EXCHANGE_TIMEOUT_MS);
|
|
173
173
|
try {
|
|
174
|
+
// Present only in the app's sandbox (written to .env.local, never to the deployed
|
|
175
|
+
// env); it lets the platform tell a sandbox call from a deployed one.
|
|
176
|
+
const sandboxGrant = process.env.ASSEMBLY_SANDBOX_GRANT;
|
|
174
177
|
const resp = await fetch(`${base}/v1/token`, {
|
|
175
178
|
method: 'POST',
|
|
176
|
-
headers:
|
|
179
|
+
headers: sandboxGrant
|
|
180
|
+
? { 'X-API-Key': apiKey, 'Content-Type': 'application/json' }
|
|
181
|
+
: { 'X-API-Key': apiKey },
|
|
182
|
+
...(sandboxGrant ? { body: JSON.stringify({ sandboxGrant }) } : {}),
|
|
177
183
|
signal: controller.signal,
|
|
178
184
|
});
|
|
179
185
|
if (!resp.ok)
|
package/dist/pdf.d.ts
CHANGED
|
@@ -44,8 +44,12 @@ export declare class PdfUnreadableError extends Error {
|
|
|
44
44
|
* Finds the fields a person has to fill in on a PDF form: its AcroForm fields when
|
|
45
45
|
* it has them, otherwise fields read from its text by Assembly AI when `assembly`
|
|
46
46
|
* is given. Throws PdfUnreadableError when the bytes are not a readable PDF.
|
|
47
|
-
* Server side only: the AI step runs through the
|
|
47
|
+
* Server side only: the AI step runs through the installer client.
|
|
48
48
|
*/
|
|
49
49
|
export declare function detectPdfFields(data: ArrayBuffer | Uint8Array, options?: {
|
|
50
|
+
/**
|
|
51
|
+
* @deprecated The client is ignored: the AI step uses the installer client, since the AI proxy
|
|
52
|
+
* is app-owned. Kept so existing apps that pass it keep working; its presence still opts in.
|
|
53
|
+
*/
|
|
50
54
|
assembly?: AssemblyAPI;
|
|
51
55
|
}): Promise<PdfFieldDetection>;
|
package/dist/pdf.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getInstallerAssemblyForCaller } from './installer-caller.js';
|
|
1
2
|
// pdf.js rejects a Node Buffer outright, so hand it a plain Uint8Array view.
|
|
2
3
|
function toBytes(data) {
|
|
3
4
|
return data instanceof ArrayBuffer
|
|
@@ -98,7 +99,7 @@ const AI_RESPONSE_FORMAT = {
|
|
|
98
99
|
* Finds the fields a person has to fill in on a PDF form: its AcroForm fields when
|
|
99
100
|
* it has them, otherwise fields read from its text by Assembly AI when `assembly`
|
|
100
101
|
* is given. Throws PdfUnreadableError when the bytes are not a readable PDF.
|
|
101
|
-
* Server side only: the AI step runs through the
|
|
102
|
+
* Server side only: the AI step runs through the installer client.
|
|
102
103
|
*/
|
|
103
104
|
export async function detectPdfFields(data, options) {
|
|
104
105
|
const bytes = toBytes(data);
|
|
@@ -137,7 +138,7 @@ export async function detectPdfFields(data, options) {
|
|
|
137
138
|
return { method: 'none', fields: [], pages, text, warnings };
|
|
138
139
|
if (!options?.assembly)
|
|
139
140
|
return { method: 'text', fields: [], pages, text, warnings };
|
|
140
|
-
const suggested = await suggestFieldsFromText(
|
|
141
|
+
const suggested = await suggestFieldsFromText(pageTexts);
|
|
141
142
|
return {
|
|
142
143
|
method: 'text',
|
|
143
144
|
fields: suggested.fields,
|
|
@@ -247,7 +248,7 @@ async function extractPageTexts(bytes) {
|
|
|
247
248
|
await pdf.loadingTask.destroy();
|
|
248
249
|
}
|
|
249
250
|
}
|
|
250
|
-
async function suggestFieldsFromText(
|
|
251
|
+
async function suggestFieldsFromText(pageTexts) {
|
|
251
252
|
const warnings = [];
|
|
252
253
|
const prompt = pageTexts
|
|
253
254
|
.map((pageText, index) => {
|
|
@@ -261,6 +262,9 @@ async function suggestFieldsFromText(assembly, pageTexts) {
|
|
|
261
262
|
.join('\n\n');
|
|
262
263
|
let suggested;
|
|
263
264
|
try {
|
|
265
|
+
// Inside the try so a missing verified viewer degrades to a warning like any other AI failure.
|
|
266
|
+
// Same viewer gate as storage and connectors: an app with token exchange off has no verified viewer here, and that is accepted.
|
|
267
|
+
const assembly = await getInstallerAssemblyForCaller();
|
|
264
268
|
const response = await assembly.fetch(AI_CHAT_COMPLETIONS_PATH, {
|
|
265
269
|
method: 'POST',
|
|
266
270
|
headers: { 'Content-Type': 'application/json' },
|
package/dist/scripts.js
CHANGED
|
@@ -4652,9 +4652,11 @@ async function mintBearerFromAppKey() {
|
|
|
4652
4652
|
const controller = new AbortController();
|
|
4653
4653
|
const timeoutId = setTimeout(() => controller.abort(), EXCHANGE_TIMEOUT_MS);
|
|
4654
4654
|
try {
|
|
4655
|
+
const sandboxGrant = process.env.ASSEMBLY_SANDBOX_GRANT;
|
|
4655
4656
|
const resp = await fetch(`${base}/v1/token`, {
|
|
4656
4657
|
method: "POST",
|
|
4657
|
-
headers: { "X-API-Key": apiKey },
|
|
4658
|
+
headers: sandboxGrant ? { "X-API-Key": apiKey, "Content-Type": "application/json" } : { "X-API-Key": apiKey },
|
|
4659
|
+
...sandboxGrant ? { body: JSON.stringify({ sandboxGrant }) } : {},
|
|
4658
4660
|
signal: controller.signal
|
|
4659
4661
|
});
|
|
4660
4662
|
if (!resp.ok) return null;
|
package/package.json
CHANGED
|
@@ -13,13 +13,15 @@ The proxy mirrors OpenAI's API shape. Call any OpenAI endpoint path via:
|
|
|
13
13
|
|
|
14
14
|
## Setup
|
|
15
15
|
|
|
16
|
-
Create a helper in `lib/ai.ts` that
|
|
16
|
+
Create a helper in `lib/ai.ts` that calls the proxy on the installer client:
|
|
17
17
|
|
|
18
18
|
```typescript
|
|
19
|
-
import { getAssembly } from '@assembly-js/app-foundation/server';
|
|
19
|
+
import { getAssembly, getInstallerAssembly } from '@assembly-js/app-foundation/server';
|
|
20
20
|
|
|
21
21
|
export async function chatCompletion(messages: Array<{ role: string; content: string }>, options?: { model?: string; stream?: boolean }) {
|
|
22
|
-
|
|
22
|
+
// The AI proxy is app-owned, so it uses the installer client; getAssembly() is the gate, it throws without a verified viewer.
|
|
23
|
+
await getAssembly();
|
|
24
|
+
const assembly = await getInstallerAssembly();
|
|
23
25
|
const response = await assembly.fetch('/v1/ai/openai/chat/completions', {
|
|
24
26
|
method: 'POST',
|
|
25
27
|
body: JSON.stringify({
|
|
@@ -41,7 +43,9 @@ export async function aiErrorMessage(response: Response): Promise<string> {
|
|
|
41
43
|
}
|
|
42
44
|
```
|
|
43
45
|
|
|
44
|
-
`assembly.fetch()` automatically prepends the base URL and injects auth headers (`Authorization: Bearer` from
|
|
46
|
+
`assembly.fetch()` automatically prepends the base URL and injects auth headers (`Authorization: Bearer` from the installer client, plus `X-Assembly-SDK-Version`). It never sends `X-API-Key` on data calls. You only provide the path and request body.
|
|
47
|
+
|
|
48
|
+
The AI proxy belongs to the app, not to the person using it, so these calls use the installer client from `getInstallerAssembly()` rather than the viewer's `getAssembly()` client. It is server-side only. Call `await getAssembly()` first: it throws when there is no verified viewer, which keeps the installer's authority from being used by an anonymous caller. Let that throw propagate out of the server action so the bearer renewal can retry it; return `{ ok: false, message }` only for the proxy's own refusals.
|
|
45
49
|
|
|
46
50
|
## Server-side only
|
|
47
51
|
|
|
@@ -87,7 +91,9 @@ The proxy supports OpenAI's vision API. Use `gpt-4o` with the multi-part content
|
|
|
87
91
|
|
|
88
92
|
```typescript
|
|
89
93
|
export async function analyzeImage(base64DataUrl: string, prompt: string) {
|
|
90
|
-
|
|
94
|
+
// The AI proxy is app-owned, so it uses the installer client; getAssembly() is the gate, it throws without a verified viewer.
|
|
95
|
+
await getAssembly();
|
|
96
|
+
const assembly = await getInstallerAssembly();
|
|
91
97
|
const response = await assembly.fetch('/v1/ai/openai/chat/completions', {
|
|
92
98
|
method: 'POST',
|
|
93
99
|
body: JSON.stringify({
|
|
@@ -128,7 +134,9 @@ Transcribe on the server. Post the file as `multipart/form-data` to `/v1/ai/open
|
|
|
128
134
|
|
|
129
135
|
```typescript
|
|
130
136
|
export async function transcribeAudio(file: File) {
|
|
131
|
-
|
|
137
|
+
// The AI proxy is app-owned, so it uses the installer client; getAssembly() is the gate, it throws without a verified viewer.
|
|
138
|
+
await getAssembly();
|
|
139
|
+
const assembly = await getInstallerAssembly();
|
|
132
140
|
const body = new FormData();
|
|
133
141
|
body.append('file', file);
|
|
134
142
|
body.append('model', 'gpt-4o-mini-transcribe');
|
|
@@ -168,8 +168,9 @@ free — no second upload.
|
|
|
168
168
|
### Detecting form fields in an uploaded PDF
|
|
169
169
|
|
|
170
170
|
A PDF-to-form feature calls the helper in the processing route and lets `method` decide the UI. Passing
|
|
171
|
-
`assembly` (from `getAssemblyForRequest(request)`)
|
|
172
|
-
it, `text` comes back with no fields.
|
|
171
|
+
`assembly` (from `getAssemblyForRequest(request)`) opts a text-only PDF in to AI-suggested fields; without
|
|
172
|
+
it, `text` comes back with no fields. The AI step runs on the installer client, so the client you pass is
|
|
173
|
+
only the opt-in flag and is otherwise ignored (the option is deprecated). A file that is not a PDF throws `PdfUnreadableError`, whose message
|
|
173
174
|
is safe to show.
|
|
174
175
|
|
|
175
176
|
```typescript
|