@nyte-ai/host 0.0.18 → 0.0.19
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/THIRD-PARTY-NOTICES.md +66 -0
- package/dist/chunks/{canvas-preview-5hxat7hh.js → canvas-preview-11nadadc.js} +8 -6
- package/dist/chunks/{canvas-preview-rp6fz0w5.js → canvas-preview-2jwbe14k.js} +5 -5
- package/dist/chunks/{canvas-preview-m02v4yy6.js → canvas-preview-55s0y68g.js} +3 -2
- package/dist/chunks/{canvas-preview-xn10d4f8.js → canvas-preview-5ekjx6sw.js} +9 -4
- package/dist/chunks/{canvas-preview-75db60xy.js → canvas-preview-8xc0vn7v.js} +5 -4
- package/dist/chunks/{canvas-preview-zn2d0k9d.js → canvas-preview-af86jz8t.js} +14 -6
- package/dist/chunks/{canvas-preview-d9wwhbtz.js → canvas-preview-aq5j4p25.js} +1 -1
- package/dist/chunks/{canvas-preview-j1tnf51w.js → canvas-preview-b2v8wfnz.js} +1 -1
- package/dist/chunks/canvas-preview-befkjea2.js +60 -0
- package/dist/chunks/canvas-preview-bxnqt8cd.js +103 -0
- package/dist/chunks/{canvas-preview-vax9ne4e.js → canvas-preview-cawy6vbz.js} +22 -11
- package/dist/chunks/{canvas-preview-senpjwep.js → canvas-preview-crmzxxt8.js} +3 -3
- package/dist/chunks/{canvas-preview-ymgz0937.js → canvas-preview-drctegz4.js} +8 -4
- package/dist/chunks/{canvas-preview-yrfz2ak6.js → canvas-preview-e3vnkkc5.js} +6 -6
- package/dist/chunks/canvas-preview-ec2mt7es.js +108 -0
- package/dist/chunks/{canvas-preview-1ta3avah.js → canvas-preview-fxa0rfb0.js} +10 -5
- package/dist/chunks/{canvas-preview-8xgt67ka.js → canvas-preview-g5fkjs34.js} +1 -1
- package/dist/chunks/{canvas-preview-g3qj3y3k.js → canvas-preview-jeddjex8.js} +13 -3
- package/dist/chunks/{canvas-preview-0nrnbt76.js → canvas-preview-k1yg4fgp.js} +199 -125
- package/dist/chunks/canvas-preview-kp75gb63.js +141 -0
- package/dist/chunks/{canvas-preview-ewgspmsd.js → canvas-preview-n2a35zv5.js} +1 -1
- package/dist/chunks/{canvas-preview-h8hshnff.js → canvas-preview-pkzqg96v.js} +1 -1
- package/dist/chunks/{canvas-preview-evh92ywd.js → canvas-preview-tnqe7k0b.js} +15 -10
- package/dist/chunks/{canvas-preview-ehf3vp31.js → canvas-preview-vm9zdp2t.js} +45 -58
- package/dist/chunks/{canvas-preview-76fwvd6e.js → canvas-preview-w54ymtb3.js} +1 -1
- package/dist/chunks/canvas-preview-y5y6ghaw.js +125 -0
- package/dist/chunks/{canvas-preview-98aj5p34.js → canvas-preview-ycepb05s.js} +1 -1
- package/dist/src/canvas-preview.js +7 -3
- package/dist/src/deploy/cloudflare/worker.js +1 -1
- package/dist/src/deploy/vercel/server/routes/[...].js +2 -2
- package/dist/src/deploy/vercel/server/utils/host.js +1 -1
- package/dist/src/deploy/vercel/server/workflows/drive.js +3 -3
- package/dist/src/images/exif.d.ts +1 -0
- package/dist/src/images/exif.js +6 -0
- package/dist/src/images/node.d.ts +37 -0
- package/dist/src/images/node.js +12 -0
- package/dist/src/images/photon.d.ts +3 -0
- package/dist/src/images/photon.js +7 -0
- package/dist/src/images/worker.d.ts +1 -0
- package/dist/src/images/worker.js +23 -0
- package/dist/src/index.d.ts +0 -1
- package/dist/src/index.js +23 -22
- package/dist/src/listen-fetch.js +3 -3
- package/dist/src/listen.js +4 -4
- package/dist/src/local/command.d.ts +5 -0
- package/dist/src/local/command.js +6 -2
- package/dist/src/local/files.js +2 -2
- package/dist/src/local/git-watch.d.ts +1 -1
- package/dist/src/local/git-watch.js +1 -1
- package/dist/src/local/git.js +3 -3
- package/dist/src/local/github.js +2 -2
- package/dist/src/local/jobs.d.ts +1 -1
- package/dist/src/local/jobs.js +2 -1
- package/dist/src/local/provider.js +9 -9
- package/dist/src/local/snapshots.js +2 -2
- package/dist/src/local/worktrees.js +2 -2
- package/dist/src/mcp/connect.d.ts +0 -13
- package/dist/src/mcp/connect.js +1 -1
- package/dist/src/mcp/hub.js +2 -2
- package/dist/src/open.d.ts +2 -30
- package/dist/src/open.js +20 -18
- package/dist/src/serve/binding.js +1 -1
- package/dist/src/serve/handler.js +2 -2
- package/dist/src/sqlite.d.ts +2 -1
- package/dist/src/sqlite.js +4 -3
- package/dist/src/store-usage.js +4 -1
- package/dist/src/usage-history.d.ts +54 -0
- package/dist/src/usage-history.js +14 -0
- package/dist/src/usage-service.d.ts +2 -1
- package/dist/src/usage-service.js +7 -3
- package/dist/src/usage-worker.js +8 -4
- package/dist/src/usage.d.ts +150 -27
- package/dist/src/usage.js +12 -2
- package/package.json +16 -8
- package/src/canvas-preview.ts +4 -3
- package/src/images/exif.ts +69 -0
- package/src/images/node.ts +163 -0
- package/src/images/photon.ts +141 -0
- package/src/images/worker.ts +29 -0
- package/src/index.ts +19 -1
- package/src/local/command.ts +64 -12
- package/src/local/files.ts +8 -3
- package/src/local/git-watch.ts +16 -6
- package/src/local/git.ts +7 -2
- package/src/local/github.ts +3 -4
- package/src/local/jobs.ts +55 -69
- package/src/local/snapshots.ts +4 -2
- package/src/local/worktrees.ts +4 -4
- package/src/mcp/connect.ts +1 -14
- package/src/open.ts +16 -36
- package/src/serve/binding.ts +4 -3
- package/src/sqlite.ts +33 -8
- package/src/store-usage.ts +5 -2
- package/src/usage-history.ts +219 -0
- package/src/usage-service.ts +12 -3
- package/src/usage.ts +251 -176
- package/dist/chunks/canvas-preview-a3yw0j70.js +0 -72
- package/dist/chunks/canvas-preview-azg56359.js +0 -68
- package/dist/src/store-owner.d.ts +0 -15
- package/dist/src/store-owner.js +0 -9
- package/src/store-owner.ts +0 -98
package/dist/src/usage-worker.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import"../chunks/canvas-preview-srgfpdmf.js";
|
|
2
1
|
import"../chunks/canvas-preview-69fkk9yn.js";
|
|
3
|
-
import"../chunks/canvas-preview-
|
|
4
|
-
import"../chunks/canvas-preview-
|
|
2
|
+
import"../chunks/canvas-preview-zaaj99wd.js";
|
|
3
|
+
import"../chunks/canvas-preview-0cmbkp2j.js";
|
|
4
|
+
import"../chunks/canvas-preview-tnqe7k0b.js";
|
|
5
|
+
import"../chunks/canvas-preview-srgfpdmf.js";
|
|
6
|
+
import"../chunks/canvas-preview-11nadadc.js";
|
|
7
|
+
import"../chunks/canvas-preview-kp75gb63.js";
|
|
8
|
+
import"../chunks/canvas-preview-k1yg4fgp.js";
|
|
5
9
|
import {
|
|
6
10
|
UsageScanner2
|
|
7
|
-
} from "../chunks/canvas-preview-
|
|
11
|
+
} from "../chunks/canvas-preview-fxa0rfb0.js";
|
|
8
12
|
|
|
9
13
|
// src/usage-worker.ts
|
|
10
14
|
import { parentPort } from "node:worker_threads";
|
package/dist/src/usage.d.ts
CHANGED
|
@@ -1,29 +1,83 @@
|
|
|
1
|
+
import { Type, type Static } from "typebox";
|
|
1
2
|
import { type Models } from "@nyte-ai/ai";
|
|
2
|
-
import
|
|
3
|
-
import type {
|
|
3
|
+
import { UsageScanCache } from "./usage-history.ts";
|
|
4
|
+
import type { LocalHistoryUsage, UsageWindow } from "./usage-types.ts";
|
|
4
5
|
export interface ClaudeCodeUsageOptions {
|
|
5
6
|
readonly models: Pick<Models, "getModels">;
|
|
7
|
+
readonly window: UsageWindow;
|
|
6
8
|
readonly configDir?: string;
|
|
7
9
|
readonly signal?: AbortSignal;
|
|
8
10
|
readonly cache?: UsageScanCache<ClaudeCodeFileScan>;
|
|
9
11
|
}
|
|
10
|
-
export type UsageScanCache<T> = Map<string, FileScan<T>>;
|
|
11
|
-
export interface FileScan<T> {
|
|
12
|
-
readonly size: number;
|
|
13
|
-
readonly mtimeMs: number;
|
|
14
|
-
readonly scan: T;
|
|
15
|
-
}
|
|
16
|
-
export interface ClaudeCodeFileScan {
|
|
17
|
-
readonly keyed: readonly (readonly [string, Snapshot])[];
|
|
18
|
-
readonly anonymous: readonly Snapshot[];
|
|
19
|
-
readonly malformedRecords: number;
|
|
20
|
-
}
|
|
21
12
|
export type ClaudeCodeUsage = LocalHistoryUsage;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
13
|
+
declare const snapshot: Type.TObject<{
|
|
14
|
+
model: Type.TString;
|
|
15
|
+
usage: Type.TObject<{
|
|
16
|
+
input: Type.TNumber;
|
|
17
|
+
output: Type.TNumber;
|
|
18
|
+
cacheRead: Type.TNumber;
|
|
19
|
+
cacheWrite: Type.TNumber;
|
|
20
|
+
cacheWrite1h: Type.TOptional<Type.TNumber>;
|
|
21
|
+
reasoning: Type.TOptional<Type.TNumber>;
|
|
22
|
+
totalTokens: Type.TNumber;
|
|
23
|
+
cost: Type.TObject<{
|
|
24
|
+
input: Type.TNumber;
|
|
25
|
+
output: Type.TNumber;
|
|
26
|
+
cacheRead: Type.TNumber;
|
|
27
|
+
cacheWrite: Type.TNumber;
|
|
28
|
+
total: Type.TNumber;
|
|
29
|
+
}>;
|
|
30
|
+
}>;
|
|
31
|
+
costUSD: Type.TOptional<Type.TNumber>;
|
|
32
|
+
at: Type.TUnion<[Type.TNumber, Type.TNull]>;
|
|
33
|
+
}>;
|
|
34
|
+
export type Snapshot = Static<typeof snapshot>;
|
|
35
|
+
declare const claudeCodeFileScan: Type.TObject<{
|
|
36
|
+
keyed: Type.TArray<Type.TTuple<[Type.TString, Type.TObject<{
|
|
37
|
+
model: Type.TString;
|
|
38
|
+
usage: Type.TObject<{
|
|
39
|
+
input: Type.TNumber;
|
|
40
|
+
output: Type.TNumber;
|
|
41
|
+
cacheRead: Type.TNumber;
|
|
42
|
+
cacheWrite: Type.TNumber;
|
|
43
|
+
cacheWrite1h: Type.TOptional<Type.TNumber>;
|
|
44
|
+
reasoning: Type.TOptional<Type.TNumber>;
|
|
45
|
+
totalTokens: Type.TNumber;
|
|
46
|
+
cost: Type.TObject<{
|
|
47
|
+
input: Type.TNumber;
|
|
48
|
+
output: Type.TNumber;
|
|
49
|
+
cacheRead: Type.TNumber;
|
|
50
|
+
cacheWrite: Type.TNumber;
|
|
51
|
+
total: Type.TNumber;
|
|
52
|
+
}>;
|
|
53
|
+
}>;
|
|
54
|
+
costUSD: Type.TOptional<Type.TNumber>;
|
|
55
|
+
at: Type.TUnion<[Type.TNumber, Type.TNull]>;
|
|
56
|
+
}>]>>;
|
|
57
|
+
anonymous: Type.TArray<Type.TObject<{
|
|
58
|
+
model: Type.TString;
|
|
59
|
+
usage: Type.TObject<{
|
|
60
|
+
input: Type.TNumber;
|
|
61
|
+
output: Type.TNumber;
|
|
62
|
+
cacheRead: Type.TNumber;
|
|
63
|
+
cacheWrite: Type.TNumber;
|
|
64
|
+
cacheWrite1h: Type.TOptional<Type.TNumber>;
|
|
65
|
+
reasoning: Type.TOptional<Type.TNumber>;
|
|
66
|
+
totalTokens: Type.TNumber;
|
|
67
|
+
cost: Type.TObject<{
|
|
68
|
+
input: Type.TNumber;
|
|
69
|
+
output: Type.TNumber;
|
|
70
|
+
cacheRead: Type.TNumber;
|
|
71
|
+
cacheWrite: Type.TNumber;
|
|
72
|
+
total: Type.TNumber;
|
|
73
|
+
}>;
|
|
74
|
+
}>;
|
|
75
|
+
costUSD: Type.TOptional<Type.TNumber>;
|
|
76
|
+
at: Type.TUnion<[Type.TNumber, Type.TNull]>;
|
|
77
|
+
}>>;
|
|
78
|
+
malformedRecords: Type.TInteger;
|
|
79
|
+
}>;
|
|
80
|
+
export type ClaudeCodeFileScan = Static<typeof claudeCodeFileScan>;
|
|
27
81
|
/**
|
|
28
82
|
* Reads Claude Code's local usage under `<configDir>/projects`. The configured root may be a
|
|
29
83
|
* symlink (as may a system ancestor such as macOS `/tmp`), but links below it are skipped, to
|
|
@@ -35,19 +89,82 @@ export declare function readClaudeCodeUsage(options: ClaudeCodeUsageOptions): Pr
|
|
|
35
89
|
export type CodexUsage = LocalHistoryUsage;
|
|
36
90
|
export interface CodexUsageOptions {
|
|
37
91
|
readonly models: Pick<Models, "getModels">;
|
|
92
|
+
readonly window: UsageWindow;
|
|
38
93
|
readonly homeDir?: string;
|
|
39
94
|
readonly signal?: AbortSignal;
|
|
40
95
|
readonly cache?: UsageScanCache<CodexFileScan>;
|
|
41
96
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
97
|
+
declare const codexRow: Type.TObject<{
|
|
98
|
+
model: Type.TString;
|
|
99
|
+
usage: Type.TObject<{
|
|
100
|
+
input: Type.TNumber;
|
|
101
|
+
output: Type.TNumber;
|
|
102
|
+
cacheRead: Type.TNumber;
|
|
103
|
+
cacheWrite: Type.TNumber;
|
|
104
|
+
cacheWrite1h: Type.TOptional<Type.TNumber>;
|
|
105
|
+
reasoning: Type.TOptional<Type.TNumber>;
|
|
106
|
+
totalTokens: Type.TNumber;
|
|
107
|
+
cost: Type.TObject<{
|
|
108
|
+
input: Type.TNumber;
|
|
109
|
+
output: Type.TNumber;
|
|
110
|
+
cacheRead: Type.TNumber;
|
|
111
|
+
cacheWrite: Type.TNumber;
|
|
112
|
+
total: Type.TNumber;
|
|
113
|
+
}>;
|
|
114
|
+
}>;
|
|
115
|
+
at: Type.TUnion<[Type.TNumber, Type.TNull]>;
|
|
116
|
+
}>;
|
|
117
|
+
export type CodexRow = Static<typeof codexRow>;
|
|
118
|
+
declare const codexFileScan: Type.TObject<{
|
|
119
|
+
modern: Type.TArray<Type.TTuple<[Type.TString, Type.TObject<{
|
|
120
|
+
model: Type.TString;
|
|
121
|
+
usage: Type.TObject<{
|
|
122
|
+
input: Type.TNumber;
|
|
123
|
+
output: Type.TNumber;
|
|
124
|
+
cacheRead: Type.TNumber;
|
|
125
|
+
cacheWrite: Type.TNumber;
|
|
126
|
+
cacheWrite1h: Type.TOptional<Type.TNumber>;
|
|
127
|
+
reasoning: Type.TOptional<Type.TNumber>;
|
|
128
|
+
totalTokens: Type.TNumber;
|
|
129
|
+
cost: Type.TObject<{
|
|
130
|
+
input: Type.TNumber;
|
|
131
|
+
output: Type.TNumber;
|
|
132
|
+
cacheRead: Type.TNumber;
|
|
133
|
+
cacheWrite: Type.TNumber;
|
|
134
|
+
total: Type.TNumber;
|
|
135
|
+
}>;
|
|
136
|
+
}>;
|
|
137
|
+
at: Type.TUnion<[Type.TNumber, Type.TNull]>;
|
|
138
|
+
}>]>>;
|
|
139
|
+
legacy: Type.TArray<Type.TObject<{
|
|
140
|
+
model: Type.TString;
|
|
141
|
+
usage: Type.TObject<{
|
|
142
|
+
input: Type.TNumber;
|
|
143
|
+
output: Type.TNumber;
|
|
144
|
+
cacheRead: Type.TNumber;
|
|
145
|
+
cacheWrite: Type.TNumber;
|
|
146
|
+
cacheWrite1h: Type.TOptional<Type.TNumber>;
|
|
147
|
+
reasoning: Type.TOptional<Type.TNumber>;
|
|
148
|
+
totalTokens: Type.TNumber;
|
|
149
|
+
cost: Type.TObject<{
|
|
150
|
+
input: Type.TNumber;
|
|
151
|
+
output: Type.TNumber;
|
|
152
|
+
cacheRead: Type.TNumber;
|
|
153
|
+
cacheWrite: Type.TNumber;
|
|
154
|
+
total: Type.TNumber;
|
|
155
|
+
}>;
|
|
156
|
+
}>;
|
|
157
|
+
at: Type.TUnion<[Type.TNumber, Type.TNull]>;
|
|
158
|
+
}>>;
|
|
159
|
+
malformedRecords: Type.TInteger;
|
|
160
|
+
cursor: Type.TObject<{
|
|
161
|
+
model: Type.TString;
|
|
162
|
+
sawMeta: Type.TBoolean;
|
|
163
|
+
signature: Type.TOptional<Type.TString>;
|
|
164
|
+
forkAnchorMs: Type.TOptional<Type.TNumber>;
|
|
165
|
+
}>;
|
|
166
|
+
}>;
|
|
167
|
+
export type CodexFileScan = Static<typeof codexFileScan>;
|
|
51
168
|
/**
|
|
52
169
|
* Reads Codex's local usage from `sessions` and `archived_sessions` (where the legacy format
|
|
53
170
|
* lives). A `response_id` is unique across files, so one map dedups a forked rollout's repeated
|
|
@@ -61,12 +178,18 @@ export interface UsageScanCaches {
|
|
|
61
178
|
readonly codex: UsageScanCache<CodexFileScan>;
|
|
62
179
|
}
|
|
63
180
|
export declare function createUsageScanCaches(): UsageScanCaches;
|
|
181
|
+
/** Restores the scans {@link saveUsageScanCaches} wrote to `path`; each still answers only while its file is unchanged. */
|
|
182
|
+
export declare function loadUsageScanCaches(path: string): Promise<UsageScanCaches>;
|
|
183
|
+
/** Writes `caches` to `path` when a read changed them since they were last saved; a failed write is tried again on the next save. */
|
|
184
|
+
export declare function saveUsageScanCaches(path: string, caches: UsageScanCaches): Promise<void>;
|
|
64
185
|
export interface LocalUsage {
|
|
65
186
|
readonly claudeCode: LocalHistoryUsage;
|
|
66
187
|
readonly codex: LocalHistoryUsage;
|
|
67
188
|
}
|
|
68
189
|
export declare function readLocalUsage(options: {
|
|
69
190
|
readonly models: Pick<Models, "getModels">;
|
|
191
|
+
readonly window: UsageWindow;
|
|
70
192
|
readonly signal?: AbortSignal;
|
|
71
193
|
readonly caches?: UsageScanCaches;
|
|
72
194
|
}): Promise<LocalUsage>;
|
|
195
|
+
export {};
|
package/dist/src/usage.js
CHANGED
|
@@ -2,12 +2,22 @@ import {
|
|
|
2
2
|
readClaudeCodeUsage2,
|
|
3
3
|
readCodexUsage2,
|
|
4
4
|
createUsageScanCaches2,
|
|
5
|
+
loadUsageScanCaches2,
|
|
6
|
+
saveUsageScanCaches2,
|
|
5
7
|
readLocalUsage2
|
|
6
|
-
} from "../chunks/canvas-preview-
|
|
8
|
+
} from "../chunks/canvas-preview-k1yg4fgp.js";
|
|
9
|
+
import"../chunks/canvas-preview-11nadadc.js";
|
|
10
|
+
import"../chunks/canvas-preview-kp75gb63.js";
|
|
7
11
|
import"../chunks/canvas-preview-srgfpdmf.js";
|
|
12
|
+
import"../chunks/canvas-preview-tnqe7k0b.js";
|
|
13
|
+
import"../chunks/canvas-preview-zaaj99wd.js";
|
|
14
|
+
import"../chunks/canvas-preview-0cmbkp2j.js";
|
|
15
|
+
import"../chunks/canvas-preview-69fkk9yn.js";
|
|
8
16
|
export {
|
|
9
17
|
createUsageScanCaches2 as createUsageScanCaches,
|
|
18
|
+
loadUsageScanCaches2 as loadUsageScanCaches,
|
|
10
19
|
readClaudeCodeUsage2 as readClaudeCodeUsage,
|
|
11
20
|
readCodexUsage2 as readCodexUsage,
|
|
12
|
-
readLocalUsage2 as readLocalUsage
|
|
21
|
+
readLocalUsage2 as readLocalUsage,
|
|
22
|
+
saveUsageScanCaches2 as saveUsageScanCaches
|
|
13
23
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nyte-ai/host",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -36,6 +36,14 @@
|
|
|
36
36
|
"types": "./dist/src/identity.d.ts",
|
|
37
37
|
"import": "./dist/src/identity.js"
|
|
38
38
|
},
|
|
39
|
+
"./images": {
|
|
40
|
+
"types": "./dist/src/images/node.d.ts",
|
|
41
|
+
"import": "./dist/src/images/node.js"
|
|
42
|
+
},
|
|
43
|
+
"./image-worker": {
|
|
44
|
+
"types": "./dist/src/images/worker.d.ts",
|
|
45
|
+
"import": "./dist/src/images/worker.js"
|
|
46
|
+
},
|
|
39
47
|
"./listen-fetch": {
|
|
40
48
|
"types": "./dist/src/listen-fetch.d.ts",
|
|
41
49
|
"import": "./dist/src/listen-fetch.js"
|
|
@@ -88,11 +96,12 @@
|
|
|
88
96
|
"dependencies": {
|
|
89
97
|
"@earendil-works/pi-mcp": "1.3.0",
|
|
90
98
|
"@lydell/node-pty": "1.2.0-beta.15",
|
|
91
|
-
"@nyte-ai/ai": "0.0.
|
|
92
|
-
"@nyte-ai/core": "0.0.
|
|
93
|
-
"@nyte-ai/plugin": "0.0.
|
|
94
|
-
"@nyte-ai/protocol": "0.0.
|
|
95
|
-
"@nyte-ai/schema": "0.0.
|
|
99
|
+
"@nyte-ai/ai": "0.0.19",
|
|
100
|
+
"@nyte-ai/core": "0.0.19",
|
|
101
|
+
"@nyte-ai/plugin": "0.0.19",
|
|
102
|
+
"@nyte-ai/protocol": "0.0.19",
|
|
103
|
+
"@nyte-ai/schema": "0.0.19",
|
|
104
|
+
"@silvia-odwyer/photon": "0.3.3",
|
|
96
105
|
"fflate": "0.8.3",
|
|
97
106
|
"ignore": "7.0.9",
|
|
98
107
|
"pg": "8.23.0",
|
|
@@ -126,10 +135,9 @@
|
|
|
126
135
|
},
|
|
127
136
|
"scripts": {
|
|
128
137
|
"clean": "rm -rf node_modules .turbo",
|
|
129
|
-
"typecheck": "
|
|
138
|
+
"typecheck": "tsrs --noEmit && tsrs --noEmit -p src/deploy/vercel",
|
|
130
139
|
"test": "vitest --run",
|
|
131
140
|
"check:store": "node scripts/check-store.ts",
|
|
132
|
-
"compact:store": "node scripts/compact-store.ts",
|
|
133
141
|
"build:package": "bun ../../scripts/sdk-packages.mjs build host"
|
|
134
142
|
}
|
|
135
143
|
}
|
package/src/canvas-preview.ts
CHANGED
|
@@ -27,6 +27,7 @@ import type {
|
|
|
27
27
|
import { Type } from "typebox";
|
|
28
28
|
import type { TSchema } from "typebox";
|
|
29
29
|
import { Value } from "typebox/value";
|
|
30
|
+
import { killTree } from "./local/command.ts";
|
|
30
31
|
|
|
31
32
|
const ORIGIN = "https://canvas.preview/";
|
|
32
33
|
|
|
@@ -312,12 +313,12 @@ function stop(child: ChildProcess): Promise<void> {
|
|
|
312
313
|
if (child.exitCode !== null || child.signalCode !== null) return Promise.resolve();
|
|
313
314
|
|
|
314
315
|
return new Promise((resolve) => {
|
|
315
|
-
const kill = setTimeout(() => child
|
|
316
|
+
const kill = setTimeout(() => killTree(child, "SIGKILL"), 2000);
|
|
316
317
|
child.once("exit", () => {
|
|
317
318
|
clearTimeout(kill);
|
|
318
319
|
resolve();
|
|
319
320
|
});
|
|
320
|
-
child
|
|
321
|
+
killTree(child, "SIGTERM");
|
|
321
322
|
});
|
|
322
323
|
}
|
|
323
324
|
|
|
@@ -396,7 +397,7 @@ export function chromiumCanvasPreview(
|
|
|
396
397
|
}
|
|
397
398
|
} finally {
|
|
398
399
|
await stop(child);
|
|
399
|
-
await rm(profile, { recursive: true, force: true });
|
|
400
|
+
await rm(profile, { recursive: true, force: true, maxRetries: 10 });
|
|
400
401
|
}
|
|
401
402
|
};
|
|
402
403
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const ORIENTATION_TAG = 0x0112;
|
|
2
|
+
|
|
3
|
+
export function exifOrientation(bytes: Uint8Array): number {
|
|
4
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
5
|
+
const tiff = tiffStart(bytes, view);
|
|
6
|
+
|
|
7
|
+
if (tiff < 0 || tiff + 8 > bytes.length) return 1;
|
|
8
|
+
const little = bytes[tiff] === 0x49 && bytes[tiff + 1] === 0x49;
|
|
9
|
+
const directory = tiff + view.getUint32(tiff + 4, little);
|
|
10
|
+
|
|
11
|
+
if (directory + 2 > bytes.length) return 1;
|
|
12
|
+
const entries = view.getUint16(directory, little);
|
|
13
|
+
|
|
14
|
+
for (let index = 0; index < entries; index++) {
|
|
15
|
+
const entry = directory + 2 + index * 12;
|
|
16
|
+
|
|
17
|
+
if (entry + 12 > bytes.length) return 1;
|
|
18
|
+
|
|
19
|
+
if (view.getUint16(entry, little) === ORIENTATION_TAG) {
|
|
20
|
+
const value = view.getUint16(entry + 8, little);
|
|
21
|
+
|
|
22
|
+
return value >= 1 && value <= 8 ? value : 1;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return 1;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function tiffStart(bytes: Uint8Array, view: DataView): number {
|
|
30
|
+
if (bytes[0] === 0xff && bytes[1] === 0xd8) {
|
|
31
|
+
for (let offset = 2; offset + 4 <= bytes.length;) {
|
|
32
|
+
if (bytes[offset] !== 0xff) return -1;
|
|
33
|
+
const marker = view.getUint8(offset + 1);
|
|
34
|
+
|
|
35
|
+
if (marker === 0xff) {
|
|
36
|
+
offset++;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (marker === 0xe1 && startsExif(bytes, offset + 4)) return offset + 10;
|
|
41
|
+
offset += 2 + view.getUint16(offset + 2);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return -1;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (ascii(bytes, 0, "RIFF") && ascii(bytes, 8, "WEBP")) {
|
|
48
|
+
for (let offset = 12; offset + 8 <= bytes.length;) {
|
|
49
|
+
const size = view.getUint32(offset + 4, true);
|
|
50
|
+
|
|
51
|
+
if (ascii(bytes, offset, "EXIF"))
|
|
52
|
+
return startsExif(bytes, offset + 8) ? offset + 14 : offset + 8;
|
|
53
|
+
offset += 8 + size + (size % 2);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return -1;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function startsExif(bytes: Uint8Array, offset: number): boolean {
|
|
61
|
+
return ascii(bytes, offset, "Exif") && bytes[offset + 4] === 0 && bytes[offset + 5] === 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function ascii(bytes: Uint8Array, offset: number, text: string): boolean {
|
|
65
|
+
for (let index = 0; index < text.length; index++)
|
|
66
|
+
if (bytes[offset + index] !== text.charCodeAt(index)) return false;
|
|
67
|
+
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { Worker } from "node:worker_threads";
|
|
4
|
+
import type { ImageLimits, ImageProcessor, PreparedImage } from "@nyte-ai/core";
|
|
5
|
+
import { Type } from "typebox";
|
|
6
|
+
import { Value } from "typebox/value";
|
|
7
|
+
import { photonImages } from "./photon.ts";
|
|
8
|
+
|
|
9
|
+
export interface NodeImagesOptions {
|
|
10
|
+
readonly worker?: string | URL;
|
|
11
|
+
readonly wasm?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface PrepareRequest {
|
|
15
|
+
readonly id: number;
|
|
16
|
+
readonly bytes: Uint8Array;
|
|
17
|
+
readonly mimeType: string;
|
|
18
|
+
readonly limits: ImageLimits;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const size = Type.Object({ width: Type.Number(), height: Type.Number() });
|
|
22
|
+
|
|
23
|
+
export const prepareReply = Type.Union([
|
|
24
|
+
Type.Object({ id: Type.Integer(), result: Type.Null() }),
|
|
25
|
+
Type.Object({
|
|
26
|
+
id: Type.Integer(),
|
|
27
|
+
result: Type.Object({
|
|
28
|
+
data: Type.String(),
|
|
29
|
+
mimeType: Type.String(),
|
|
30
|
+
resized: Type.Optional(Type.Object({ from: size, to: size })),
|
|
31
|
+
}),
|
|
32
|
+
}),
|
|
33
|
+
Type.Object({ id: Type.Integer(), error: Type.String() }),
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
const IDLE_MS = 30_000;
|
|
37
|
+
|
|
38
|
+
export function nodeImages(options: NodeImagesOptions = {}): ImageProcessor {
|
|
39
|
+
const worker =
|
|
40
|
+
options.worker ??
|
|
41
|
+
new URL(import.meta.url.endsWith(".ts") ? "./worker.ts" : "./worker.js", import.meta.url);
|
|
42
|
+
|
|
43
|
+
let wasm: Promise<Uint8Array> | undefined;
|
|
44
|
+
|
|
45
|
+
const loadWasm = (): Promise<Uint8Array> =>
|
|
46
|
+
(wasm ??= readFile(options.wasm ?? resolvePhotonWasmPath()));
|
|
47
|
+
|
|
48
|
+
let thread: PhotonThread | undefined;
|
|
49
|
+
let inProcess: ImageProcessor | undefined;
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
async prepare(bytes, mimeType, limits) {
|
|
53
|
+
if (inProcess !== undefined) return inProcess.prepare(bytes, mimeType, limits);
|
|
54
|
+
const module = await loadWasm();
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
thread ??= new PhotonThread(worker, module, (ended) => {
|
|
58
|
+
if (thread === ended) thread = undefined;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
return await thread.prepare(bytes, mimeType, limits);
|
|
62
|
+
} catch (cause) {
|
|
63
|
+
if (!(cause instanceof WorkerUnavailable)) throw cause;
|
|
64
|
+
inProcess ??= photonImages(module);
|
|
65
|
+
|
|
66
|
+
return inProcess.prepare(bytes, mimeType, limits);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function resolvePhotonWasmPath(): string {
|
|
73
|
+
return fileURLToPath(import.meta.resolve("@silvia-odwyer/photon/photon_rs_bg.wasm"));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
class WorkerUnavailable extends Error {}
|
|
77
|
+
|
|
78
|
+
class PhotonThread {
|
|
79
|
+
readonly #worker: Worker;
|
|
80
|
+
readonly #pending = new Map<number, PromiseWithResolvers<PreparedImage | undefined>>();
|
|
81
|
+
readonly #onExit: (ended: PhotonThread) => void;
|
|
82
|
+
#nextId = 0;
|
|
83
|
+
#idle: ReturnType<typeof setTimeout> | undefined;
|
|
84
|
+
#started = false;
|
|
85
|
+
|
|
86
|
+
constructor(entry: string | URL, wasm: Uint8Array, onExit: (ended: PhotonThread) => void) {
|
|
87
|
+
this.#onExit = onExit;
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
this.#worker = new Worker(entry, { workerData: wasm });
|
|
91
|
+
} catch (cause) {
|
|
92
|
+
throw new WorkerUnavailable("The image worker cannot start", { cause });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.#worker.on("message", (reply: unknown) => {
|
|
96
|
+
if (!Value.Check(prepareReply, reply)) {
|
|
97
|
+
this.#fail(new Error("Malformed image worker reply"));
|
|
98
|
+
void this.#worker.terminate();
|
|
99
|
+
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this.#started = true;
|
|
104
|
+
const request = this.#pending.get(reply.id);
|
|
105
|
+
|
|
106
|
+
if (request === undefined) return;
|
|
107
|
+
this.#pending.delete(reply.id);
|
|
108
|
+
|
|
109
|
+
if ("error" in reply) request.reject(new Error(reply.error));
|
|
110
|
+
else request.resolve(reply.result ?? undefined);
|
|
111
|
+
this.#settled();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
this.#worker.on("error", (cause) => this.#fail(cause));
|
|
115
|
+
|
|
116
|
+
this.#worker.on("exit", (code) =>
|
|
117
|
+
this.#fail(new Error(`The image worker exited with code ${String(code)}`)),
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
prepare(
|
|
122
|
+
bytes: Uint8Array,
|
|
123
|
+
mimeType: string,
|
|
124
|
+
limits: ImageLimits,
|
|
125
|
+
): Promise<PreparedImage | undefined> {
|
|
126
|
+
const id = this.#nextId++;
|
|
127
|
+
const request = Promise.withResolvers<PreparedImage | undefined>();
|
|
128
|
+
this.#pending.set(id, request);
|
|
129
|
+
clearTimeout(this.#idle);
|
|
130
|
+
this.#idle = undefined;
|
|
131
|
+
this.#worker.ref();
|
|
132
|
+
const copy = bytes.slice();
|
|
133
|
+
|
|
134
|
+
this.#worker.postMessage({ id, bytes: copy, mimeType, limits } satisfies PrepareRequest, [
|
|
135
|
+
copy.buffer,
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
return request.promise;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
#settled(): void {
|
|
142
|
+
if (this.#pending.size > 0) return;
|
|
143
|
+
this.#worker.unref();
|
|
144
|
+
|
|
145
|
+
this.#idle = setTimeout(() => {
|
|
146
|
+
this.#onExit(this);
|
|
147
|
+
void this.#worker.terminate();
|
|
148
|
+
}, IDLE_MS);
|
|
149
|
+
|
|
150
|
+
this.#idle.unref();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#fail(cause: unknown): void {
|
|
154
|
+
const failure = this.#started
|
|
155
|
+
? cause
|
|
156
|
+
: new WorkerUnavailable("The image worker cannot start", { cause });
|
|
157
|
+
|
|
158
|
+
for (const request of this.#pending.values()) request.reject(failure);
|
|
159
|
+
this.#pending.clear();
|
|
160
|
+
clearTimeout(this.#idle);
|
|
161
|
+
this.#onExit(this);
|
|
162
|
+
}
|
|
163
|
+
}
|