@inferencesh/app 0.1.2 → 0.1.4

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/dist/file.d.ts CHANGED
@@ -71,6 +71,7 @@ export declare class File {
71
71
  toJSON(): FileData;
72
72
  static getCacheDir(): string;
73
73
  private _getCachePath;
74
+ private _decodeDataUri;
74
75
  private _downloadUrl;
75
76
  private _populateMetadata;
76
77
  }
package/dist/file.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { createWriteStream, existsSync, mkdirSync, statSync, renameSync, unlinkSync } from "node:fs";
2
+ import { createWriteStream, existsSync, mkdirSync, statSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
3
3
  import { basename, resolve, join } from "node:path";
4
4
  import { homedir } from "node:os";
5
5
  import { get as httpsGet } from "node:https";
@@ -74,7 +74,10 @@ export class File {
74
74
  const file = new File(options);
75
75
  // Resolve URI
76
76
  if (file.uri) {
77
- if (isUrl(file.uri)) {
77
+ if (isDataUri(file.uri)) {
78
+ file._decodeDataUri(file.uri);
79
+ }
80
+ else if (isUrl(file.uri)) {
78
81
  await file._downloadUrl(file.uri);
79
82
  }
80
83
  else {
@@ -148,6 +151,32 @@ export class File {
148
151
  mkdirSync(hashDir, { recursive: true });
149
152
  return join(hashDir, fname);
150
153
  }
154
+ // --- Data URI ---
155
+ _decodeDataUri(uri) {
156
+ const parsed = parseDataUri(uri);
157
+ // Create cache path based on hash
158
+ const hash = createHash("sha256").update(uri).digest("hex").slice(0, 16);
159
+ const cacheDir = join(File.getCacheDir(), "data_uri", hash);
160
+ // Check for existing cached file
161
+ if (existsSync(cacheDir)) {
162
+ const files = require("node:fs").readdirSync(cacheDir);
163
+ if (files.length > 0) {
164
+ this.path = join(cacheDir, files[0]);
165
+ return;
166
+ }
167
+ }
168
+ // Set content type from data URI
169
+ if (!this.contentType) {
170
+ this.contentType = parsed.mediaType;
171
+ }
172
+ // Write to cache
173
+ mkdirSync(cacheDir, { recursive: true });
174
+ const ext = getExtensionForMimeType(parsed.mediaType);
175
+ const filename = `file${ext}`;
176
+ const cachePath = join(cacheDir, filename);
177
+ writeFileSync(cachePath, parsed.data);
178
+ this.path = cachePath;
179
+ }
151
180
  // --- Download ---
152
181
  async _downloadUrl(url) {
153
182
  const cachePath = this._getCachePath(url);
@@ -191,6 +220,60 @@ export class File {
191
220
  function isUrl(s) {
192
221
  return s.startsWith("http://") || s.startsWith("https://");
193
222
  }
223
+ function isDataUri(s) {
224
+ return s.startsWith("data:");
225
+ }
226
+ /**
227
+ * Parse a data URI and return the media type and decoded data.
228
+ *
229
+ * Supports formats:
230
+ * - data:image/jpeg;base64,/9j/4AAQ...
231
+ * - data:text/plain,Hello%20World
232
+ * - data:;base64,SGVsbG8= (defaults to text/plain)
233
+ */
234
+ function parseDataUri(uri) {
235
+ const match = uri.match(/^data:([^;,]*)?(?:;(base64))?,(.*)$/s);
236
+ if (!match) {
237
+ throw new Error("Invalid data URI format");
238
+ }
239
+ const mediaType = match[1] || "text/plain";
240
+ const isBase64 = match[2] === "base64";
241
+ let dataStr = match[3];
242
+ if (isBase64) {
243
+ // Handle URL-safe base64 (- and _ instead of + and /)
244
+ dataStr = dataStr.replace(/-/g, "+").replace(/_/g, "/");
245
+ // Add padding if needed
246
+ const padding = 4 - (dataStr.length % 4);
247
+ if (padding !== 4) {
248
+ dataStr += "=".repeat(padding);
249
+ }
250
+ return { mediaType, data: Buffer.from(dataStr, "base64") };
251
+ }
252
+ else {
253
+ // URL-encoded data
254
+ return { mediaType, data: Buffer.from(decodeURIComponent(dataStr), "utf-8") };
255
+ }
256
+ }
257
+ const EXTENSION_MAP = {
258
+ "image/jpeg": ".jpg",
259
+ "image/png": ".png",
260
+ "image/gif": ".gif",
261
+ "image/webp": ".webp",
262
+ "image/svg+xml": ".svg",
263
+ "video/mp4": ".mp4",
264
+ "video/webm": ".webm",
265
+ "audio/mpeg": ".mp3",
266
+ "audio/wav": ".wav",
267
+ "audio/ogg": ".ogg",
268
+ "application/pdf": ".pdf",
269
+ "application/json": ".json",
270
+ "text/plain": ".txt",
271
+ "text/html": ".html",
272
+ "text/csv": ".csv",
273
+ };
274
+ function getExtensionForMimeType(mimeType) {
275
+ return EXTENSION_MAP[mimeType] || "";
276
+ }
194
277
  function downloadToFile(url, destPath) {
195
278
  return new Promise((resolve, reject) => {
196
279
  const parsed = new URL(url);
package/dist/index.d.ts CHANGED
@@ -3,5 +3,4 @@ export type { FileOptions, FileData } from "./file.js";
3
3
  export { StorageDir, ensureDir } from "./storage.js";
4
4
  export type { StorageDirValue } from "./storage.js";
5
5
  export { download } from "./download.js";
6
- export { textMeta, imageMeta, videoMeta, audioMeta, rawMeta } from "./output-meta.js";
7
- export type { OutputMeta, MetaItem, MetaItemBase, TextMeta, ImageMeta, VideoMeta, AudioMeta, RawMeta, } from "./output-meta.js";
6
+ export * from "./output-meta.js";
package/dist/index.js CHANGED
@@ -5,4 +5,5 @@ export { StorageDir, ensureDir } from "./storage.js";
5
5
  // Download utility
6
6
  export { download } from "./download.js";
7
7
  // Output metadata for usage-based pricing
8
- export { textMeta, imageMeta, videoMeta, audioMeta, rawMeta } from "./output-meta.js";
8
+ // (includes generated types + factory functions)
9
+ export * from "./output-meta.js";
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * Output metadata types for usage-based pricing.
3
3
  *
4
- * Apps include OutputMeta in their run output to report what was consumed
5
- * (inputs) and what was produced (outputs). The backend uses this for
6
- * pricing calculation.
4
+ * Types are generated from Go source of truth (common-go/pkg/models/usage.go)
5
+ * via `make types`. Factory functions provide type-safe constructors.
7
6
  *
8
7
  * @example
9
8
  * ```js
@@ -18,45 +17,24 @@
18
17
  * };
19
18
  * ```
20
19
  */
21
- export interface MetaItemBase {
22
- type: string;
23
- extra?: Record<string, unknown>;
24
- }
25
- export interface TextMeta extends MetaItemBase {
20
+ export * from "./types.js";
21
+ import type { MetaItem } from "./types.js";
22
+ export type TextMeta = MetaItem & {
26
23
  type: "text";
27
24
  tokens: number;
28
- }
29
- export interface ImageMeta extends MetaItemBase {
25
+ };
26
+ export type ImageMeta = MetaItem & {
30
27
  type: "image";
31
- width?: number;
32
- height?: number;
33
- resolution_mp?: number;
34
- steps?: number;
35
- count?: number;
36
- }
37
- export interface VideoMeta extends MetaItemBase {
28
+ };
29
+ export type VideoMeta = MetaItem & {
38
30
  type: "video";
39
- width?: number;
40
- height?: number;
41
- resolution_mp?: number;
42
- resolution?: "480p" | "720p" | "1080p" | "1440p" | "4k";
43
- seconds?: number;
44
- fps?: number;
45
- }
46
- export interface AudioMeta extends MetaItemBase {
31
+ };
32
+ export type AudioMeta = MetaItem & {
47
33
  type: "audio";
48
- seconds?: number;
49
- sample_rate?: number;
50
- }
51
- export interface RawMeta extends MetaItemBase {
34
+ };
35
+ export type RawMeta = MetaItem & {
52
36
  type: "raw";
53
- cost?: number;
54
- }
55
- export type MetaItem = TextMeta | ImageMeta | VideoMeta | AudioMeta | RawMeta;
56
- export interface OutputMeta {
57
- inputs?: MetaItem[];
58
- outputs?: MetaItem[];
59
- }
37
+ };
60
38
  /** Create a text metadata item. */
61
39
  export declare function textMeta(opts: Omit<TextMeta, "type">): TextMeta;
62
40
  /** Create an image metadata item. */
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * Output metadata types for usage-based pricing.
3
3
  *
4
- * Apps include OutputMeta in their run output to report what was consumed
5
- * (inputs) and what was produced (outputs). The backend uses this for
6
- * pricing calculation.
4
+ * Types are generated from Go source of truth (common-go/pkg/models/usage.go)
5
+ * via `make types`. Factory functions provide type-safe constructors.
7
6
  *
8
7
  * @example
9
8
  * ```js
@@ -18,6 +17,8 @@
18
17
  * };
19
18
  * ```
20
19
  */
20
+ // Re-export all generated types (MetaItem, MetaItemType, OutputMeta, VideoResolution, constants)
21
+ export * from "./types.js";
21
22
  // --- Factories ---
22
23
  /** Create a text metadata item. */
23
24
  export function textMeta(opts) {
@@ -0,0 +1,68 @@
1
+ export interface AppSDKTypes {
2
+ }
3
+ /**
4
+ * MetaItemType is the type discriminator for MetaItem
5
+ */
6
+ export type MetaItemType = string;
7
+ export declare const MetaItemTypeText: MetaItemType;
8
+ export declare const MetaItemTypeImage: MetaItemType;
9
+ export declare const MetaItemTypeVideo: MetaItemType;
10
+ export declare const MetaItemTypeAudio: MetaItemType;
11
+ export declare const MetaItemTypeRaw: MetaItemType;
12
+ /**
13
+ * VideoResolution represents standard video resolution presets
14
+ */
15
+ export type VideoResolution = string;
16
+ export declare const VideoRes480P: VideoResolution;
17
+ export declare const VideoRes720P: VideoResolution;
18
+ export declare const VideoRes1080P: VideoResolution;
19
+ export declare const VideoRes1440P: VideoResolution;
20
+ export declare const VideoRes4K: VideoResolution;
21
+ /**
22
+ * MetaItem represents metadata about an input or output item
23
+ */
24
+ export interface MetaItem {
25
+ type: MetaItemType;
26
+ /**
27
+ * Text fields
28
+ */
29
+ tokens?: number;
30
+ /**
31
+ * Image/Video shared fields
32
+ */
33
+ width?: number;
34
+ height?: number;
35
+ resolution_mp?: number;
36
+ /**
37
+ * Image specific fields
38
+ */
39
+ steps?: number;
40
+ count?: number;
41
+ /**
42
+ * Video specific fields
43
+ */
44
+ resolution?: VideoResolution;
45
+ seconds?: number;
46
+ fps?: number;
47
+ /**
48
+ * Audio specific fields
49
+ */
50
+ sample_rate?: number;
51
+ /**
52
+ * Raw specific fields
53
+ */
54
+ cost?: number;
55
+ /**
56
+ * App-specific key-value pairs for custom pricing factors
57
+ */
58
+ extra?: {
59
+ [key: string]: any;
60
+ };
61
+ }
62
+ /**
63
+ * OutputMeta contains structured metadata about task inputs and outputs for pricing calculation
64
+ */
65
+ export interface OutputMeta {
66
+ inputs: MetaItem[];
67
+ outputs: MetaItem[];
68
+ }
package/dist/types.js ADDED
@@ -0,0 +1,11 @@
1
+ // Code generated by tygo. DO NOT EDIT.
2
+ export const MetaItemTypeText = "text";
3
+ export const MetaItemTypeImage = "image";
4
+ export const MetaItemTypeVideo = "video";
5
+ export const MetaItemTypeAudio = "audio";
6
+ export const MetaItemTypeRaw = "raw";
7
+ export const VideoRes480P = "480p";
8
+ export const VideoRes720P = "720p";
9
+ export const VideoRes1080P = "1080p";
10
+ export const VideoRes1440P = "1440p";
11
+ export const VideoRes4K = "4k";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inferencesh/app",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "App framework for building inference.sh apps — File handling, output metadata, storage utilities",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -46,6 +46,7 @@
46
46
  },
47
47
  "files": [
48
48
  "dist",
49
+ "!dist/test",
49
50
  "README.md",
50
51
  "LICENSE"
51
52
  ]
@@ -1 +0,0 @@
1
- export {};
@@ -1,70 +0,0 @@
1
- import { describe, it, before, after } from "node:test";
2
- import assert from "node:assert";
3
- import { writeFileSync, mkdirSync, rmSync } from "node:fs";
4
- import { join } from "node:path";
5
- import { tmpdir } from "node:os";
6
- import { File } from "../file.js";
7
- const TEST_DIR = join(tmpdir(), "inferencesh-app-test-" + Date.now());
8
- let testFile;
9
- describe("File", () => {
10
- before(() => {
11
- mkdirSync(TEST_DIR, { recursive: true });
12
- testFile = join(TEST_DIR, "hello.txt");
13
- writeFileSync(testFile, "hello world");
14
- });
15
- after(() => {
16
- rmSync(TEST_DIR, { recursive: true, force: true });
17
- });
18
- it("creates from local path", () => {
19
- const file = File.fromPath(testFile);
20
- assert.ok(file.path);
21
- assert.ok(file.exists());
22
- assert.strictEqual(file.filename, "hello.txt");
23
- assert.strictEqual(file.contentType, "text/plain");
24
- assert.strictEqual(file.size, 11);
25
- });
26
- it("creates from path via async from()", async () => {
27
- const file = await File.from(testFile);
28
- assert.ok(file.path);
29
- assert.ok(file.exists());
30
- assert.strictEqual(file.filename, "hello.txt");
31
- });
32
- it("creates from options object", async () => {
33
- const file = await File.from({ path: testFile, contentType: "text/plain" });
34
- assert.ok(file.exists());
35
- assert.strictEqual(file.contentType, "text/plain");
36
- });
37
- it("creates from FileData with content_type (snake_case)", async () => {
38
- const file = await File.from({ path: testFile, content_type: "application/octet-stream" });
39
- assert.strictEqual(file.contentType, "application/octet-stream");
40
- });
41
- it("creates from another File", async () => {
42
- const original = File.fromPath(testFile);
43
- const copy = await File.from(original);
44
- assert.strictEqual(copy.path, original.path);
45
- assert.strictEqual(copy.filename, original.filename);
46
- });
47
- it("serializes to JSON with snake_case", () => {
48
- const file = File.fromPath(testFile);
49
- const json = file.toJSON();
50
- assert.ok(json.path);
51
- assert.strictEqual(json.content_type, "text/plain");
52
- assert.strictEqual(json.size, 11);
53
- assert.strictEqual(json.filename, "hello.txt");
54
- assert.strictEqual(json.uri, undefined);
55
- });
56
- it("works with JSON.stringify", () => {
57
- const file = File.fromPath(testFile);
58
- const str = JSON.stringify({ image: file });
59
- const parsed = JSON.parse(str);
60
- assert.ok(parsed.image.path);
61
- assert.strictEqual(parsed.image.content_type, "text/plain");
62
- });
63
- it("resolves relative paths to absolute", () => {
64
- const file = File.fromPath("./package.json");
65
- assert.ok(file.path.startsWith("/"));
66
- });
67
- it("throws on missing path and uri", async () => {
68
- await assert.rejects(() => File.from({}), /Either 'uri' or 'path' must be provided/);
69
- });
70
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,48 +0,0 @@
1
- import { describe, it } from "node:test";
2
- import assert from "node:assert";
3
- import { textMeta, imageMeta, videoMeta, audioMeta, rawMeta } from "../output-meta.js";
4
- describe("OutputMeta", () => {
5
- it("creates text meta", () => {
6
- const meta = textMeta({ tokens: 150 });
7
- assert.strictEqual(meta.type, "text");
8
- assert.strictEqual(meta.tokens, 150);
9
- });
10
- it("creates image meta", () => {
11
- const meta = imageMeta({ width: 1024, height: 1024, steps: 20, count: 1 });
12
- assert.strictEqual(meta.type, "image");
13
- assert.strictEqual(meta.width, 1024);
14
- assert.strictEqual(meta.steps, 20);
15
- });
16
- it("creates video meta", () => {
17
- const meta = videoMeta({ resolution: "1080p", seconds: 5.0 });
18
- assert.strictEqual(meta.type, "video");
19
- assert.strictEqual(meta.resolution, "1080p");
20
- });
21
- it("creates audio meta", () => {
22
- const meta = audioMeta({ seconds: 30.0 });
23
- assert.strictEqual(meta.type, "audio");
24
- assert.strictEqual(meta.seconds, 30.0);
25
- });
26
- it("creates raw meta", () => {
27
- const meta = rawMeta({ cost: 0.5 });
28
- assert.strictEqual(meta.type, "raw");
29
- assert.strictEqual(meta.cost, 0.5);
30
- });
31
- it("supports extra data", () => {
32
- const meta = imageMeta({ width: 512, height: 512, extra: { model: "sdxl" } });
33
- assert.strictEqual(meta.extra?.model, "sdxl");
34
- });
35
- it("composes into OutputMeta", () => {
36
- const output = {
37
- inputs: [textMeta({ tokens: 100 })],
38
- outputs: [textMeta({ tokens: 500 }), imageMeta({ width: 1024, height: 1024 })],
39
- };
40
- assert.strictEqual(output.inputs.length, 1);
41
- assert.strictEqual(output.outputs.length, 2);
42
- // Serializes cleanly
43
- const json = JSON.stringify(output);
44
- const parsed = JSON.parse(json);
45
- assert.strictEqual(parsed.inputs[0].type, "text");
46
- assert.strictEqual(parsed.outputs[1].type, "image");
47
- });
48
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,10 +0,0 @@
1
- import { describe, it } from "node:test";
2
- import assert from "node:assert";
3
- import { StorageDir } from "../storage.js";
4
- describe("StorageDir", () => {
5
- it("has correct paths", () => {
6
- assert.strictEqual(StorageDir.DATA, "/app/data");
7
- assert.strictEqual(StorageDir.TEMP, "/app/tmp");
8
- assert.strictEqual(StorageDir.CACHE, "/app/cache");
9
- });
10
- });