@phamkhachoabk/dsh-vision-describe 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/README.md +37 -0
- package/lib/engine.d.ts +45 -0
- package/lib/engine.js +12 -0
- package/lib/index.d.ts +70 -0
- package/lib/index.js +191 -0
- package/lib/schema.d.ts +8 -0
- package/lib/schema.js +22 -0
- package/lib/types.d.ts +69 -0
- package/lib/types.js +2 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# @phamkhachoabk/dsh-vision-describe
|
|
2
|
+
|
|
3
|
+
Service Definition for whole-image understanding: `ctx.visionDescribe`, the
|
|
4
|
+
engine contract, the result schema, and the fallback ladder.
|
|
5
|
+
|
|
6
|
+
This is the half OCR cannot answer. `ctx.ocr` recovers the text *in* an image;
|
|
7
|
+
this recovers what the image *is* — layout, icons, chart shapes, colors,
|
|
8
|
+
whether a dialog is an error. A consumer asks both and hands a text-only model
|
|
9
|
+
the two answers side by side.
|
|
10
|
+
|
|
11
|
+
## Using it
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const spec = ctx.visionDescribe.resolve({ prompt: 'name the error dialog' })
|
|
15
|
+
const [result] = await ctx.visionDescribe.describe([input], spec, signal)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`resolve()` is the one defaulting step: engines only ever see a complete
|
|
19
|
+
`VisionDescribeSpec`. `describe()` never throws for a failed image — an image
|
|
20
|
+
no engine could describe comes back with `status: 'failed'`, so an auxiliary
|
|
21
|
+
capability cannot break a caller's turn.
|
|
22
|
+
|
|
23
|
+
## Providing an engine
|
|
24
|
+
|
|
25
|
+
Subclass `VisionDescribeEngine` and register the instance:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
ctx.effect(() => ctx.visionDescribe.registerEngine(engine))
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Engines are ordered by `tier`, lowest first, and the ladder stops at the first
|
|
32
|
+
one whose attempt comes back `ok`. An engine that cannot run here answers
|
|
33
|
+
`unusable` from `probe()` and is recorded as `skipped` rather than called.
|
|
34
|
+
|
|
35
|
+
Today at most one engine is ever ready on a given machine, because each
|
|
36
|
+
provider is gated to the platform its runtime supports. The ladder exists so
|
|
37
|
+
that adding a provider for another platform is a registration, not a redesign.
|
package/lib/engine.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** The provider side of the vision-describe seam. @module @phamkhachoabk/dsh-vision-describe */
|
|
2
|
+
import type { EngineAvailability, VisionDescribeAttempt, VisionDescribeInput, VisionDescribeSpec } from './types.ts';
|
|
3
|
+
/**
|
|
4
|
+
* One vision-description backend. Providers subclass this and register the
|
|
5
|
+
* instance with `ctx.visionDescribe.registerEngine()`.
|
|
6
|
+
*
|
|
7
|
+
* Engines are ordered by {@link tier}: 1 is preferred. Today at most one
|
|
8
|
+
* engine is ever `ready` on a given machine (each provider is gated to the
|
|
9
|
+
* platform its runtime supports), but the ladder exists so a future provider
|
|
10
|
+
* for another platform, or a second local model, slots in without a redesign.
|
|
11
|
+
*/
|
|
12
|
+
export declare abstract class VisionDescribeEngine {
|
|
13
|
+
/** Stable identity; appears in results, cache keys and user-facing notices. */
|
|
14
|
+
abstract readonly id: string;
|
|
15
|
+
/** Preference rank; 1 is tried first. Ties break on `id` for determinism. */
|
|
16
|
+
abstract readonly tier: number;
|
|
17
|
+
/**
|
|
18
|
+
* Opaque build identity of this engine. It participates in the cache key, so
|
|
19
|
+
* changing the backend invalidates cached results without any explicit
|
|
20
|
+
* invalidation step.
|
|
21
|
+
*/
|
|
22
|
+
abstract readonly version: string;
|
|
23
|
+
/**
|
|
24
|
+
* Whether this engine can run here. Callers treat every failure alike, so a
|
|
25
|
+
* missing runtime and an unsupported platform both answer `unusable`.
|
|
26
|
+
* @param signal - cancellation for any probe work.
|
|
27
|
+
* @returns readiness, or the reason this engine is unusable.
|
|
28
|
+
*/
|
|
29
|
+
abstract probe(signal?: AbortSignal): Promise<EngineAvailability>;
|
|
30
|
+
/**
|
|
31
|
+
* Describe a batch of images in natural language.
|
|
32
|
+
* @param inputs - images to describe, in caller order.
|
|
33
|
+
* @param spec - a fully resolved request; engines apply no defaults.
|
|
34
|
+
* @param signal - cancellation for the whole batch.
|
|
35
|
+
* @returns one attempt per input, in the same order.
|
|
36
|
+
*/
|
|
37
|
+
abstract describe(inputs: readonly VisionDescribeInput[], spec: VisionDescribeSpec, signal: AbortSignal): Promise<readonly VisionDescribeAttempt[]>;
|
|
38
|
+
/**
|
|
39
|
+
* Optional background preparation, such as starting a persistent model
|
|
40
|
+
* process so a user's first image does not pay for its startup. Failures
|
|
41
|
+
* are advisory.
|
|
42
|
+
* @param signal - cancellation for the warm-up.
|
|
43
|
+
*/
|
|
44
|
+
warmUp?(signal?: AbortSignal): Promise<void>;
|
|
45
|
+
}
|
package/lib/engine.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** The provider side of the vision-describe seam. @module @phamkhachoabk/dsh-vision-describe */
|
|
2
|
+
/**
|
|
3
|
+
* One vision-description backend. Providers subclass this and register the
|
|
4
|
+
* instance with `ctx.visionDescribe.registerEngine()`.
|
|
5
|
+
*
|
|
6
|
+
* Engines are ordered by {@link tier}: 1 is preferred. Today at most one
|
|
7
|
+
* engine is ever `ready` on a given machine (each provider is gated to the
|
|
8
|
+
* platform its runtime supports), but the ladder exists so a future provider
|
|
9
|
+
* for another platform, or a second local model, slots in without a redesign.
|
|
10
|
+
*/
|
|
11
|
+
export class VisionDescribeEngine {
|
|
12
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** Vision-description capability seam (`ctx.visionDescribe`). @module @phamkhachoabk/dsh-vision-describe */
|
|
2
|
+
import { Context, Service } from '@deepseek-ai/cordis';
|
|
3
|
+
import z from '@deepseek-ai/schemastery';
|
|
4
|
+
import { VisionDescribeEngine } from './engine.ts';
|
|
5
|
+
import type { VisionDescribeInput, VisionDescribeRequest, VisionDescribeResult, VisionDescribeSpec } from './types.ts';
|
|
6
|
+
export { VisionDescribeEngine } from './engine.ts';
|
|
7
|
+
export * from './types.ts';
|
|
8
|
+
declare module '@deepseek-ai/cordis' {
|
|
9
|
+
interface Context {
|
|
10
|
+
visionDescribe: VisionDescribeService;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Deployment defaults for {@link VisionDescribeService.resolve}. These are the
|
|
15
|
+
* plugin's `Config`, not constants: a deployment changes them from `cordis.yml`.
|
|
16
|
+
*/
|
|
17
|
+
export interface VisionDescribeServiceConfig {
|
|
18
|
+
perImageTimeoutMs: number;
|
|
19
|
+
prompt: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Registry and fallback pipeline for vision-description engines.
|
|
23
|
+
*
|
|
24
|
+
* A registry rather than an abstract seam because the capability has several
|
|
25
|
+
* providers competing in a defined order, the same shape `ctx.ocr` uses.
|
|
26
|
+
*/
|
|
27
|
+
export declare class VisionDescribeService extends Service {
|
|
28
|
+
private readonly config;
|
|
29
|
+
/** Deployment defaults; the one place request defaulting is configured. */
|
|
30
|
+
static Config: z<VisionDescribeServiceConfig>;
|
|
31
|
+
private readonly registrations;
|
|
32
|
+
constructor(ctx: Context, config: VisionDescribeServiceConfig);
|
|
33
|
+
/**
|
|
34
|
+
* Add one engine to the ladder.
|
|
35
|
+
* @param engine - the provider's engine instance.
|
|
36
|
+
* @returns an idempotent disposer that removes exactly this registration.
|
|
37
|
+
*/
|
|
38
|
+
registerEngine(engine: VisionDescribeEngine): () => void;
|
|
39
|
+
/**
|
|
40
|
+
* Identity of the whole registered ladder. It participates in cache keys, so
|
|
41
|
+
* adding, removing or upgrading any engine invalidates cached results with
|
|
42
|
+
* no explicit invalidation step.
|
|
43
|
+
* @returns a stable string over every registered engine and its version.
|
|
44
|
+
*/
|
|
45
|
+
identity(): string;
|
|
46
|
+
/** Registered engines in ladder order, most preferred first. */
|
|
47
|
+
engines(): readonly VisionDescribeEngine[];
|
|
48
|
+
/**
|
|
49
|
+
* Turn a caller's wishes into a complete spec. Defaulting lives here, at the
|
|
50
|
+
* package boundary, so no engine ever sees a partially specified request.
|
|
51
|
+
* @param request - optional caller overrides.
|
|
52
|
+
* @returns a fully resolved spec.
|
|
53
|
+
*/
|
|
54
|
+
resolve(request?: VisionDescribeRequest): VisionDescribeSpec;
|
|
55
|
+
/** Warm every ready engine that offers it. Failures are advisory and logged. */
|
|
56
|
+
warmUp(signal?: AbortSignal): Promise<void>;
|
|
57
|
+
private availabilityOf;
|
|
58
|
+
/**
|
|
59
|
+
* Describe every image, advancing through the engine ladder until one
|
|
60
|
+
* succeeds. Never throws for a describe failure: an image no engine could
|
|
61
|
+
* describe comes back as a `failed` result so the caller can carry on.
|
|
62
|
+
* @param inputs - images to describe.
|
|
63
|
+
* @param spec - a resolved spec from {@link resolve}.
|
|
64
|
+
* @param signal - cancellation for the whole call.
|
|
65
|
+
* @returns one result per input, in input order.
|
|
66
|
+
*/
|
|
67
|
+
describe(inputs: readonly VisionDescribeInput[], spec: VisionDescribeSpec, signal: AbortSignal): Promise<readonly VisionDescribeResult[]>;
|
|
68
|
+
private finish;
|
|
69
|
+
}
|
|
70
|
+
export default VisionDescribeService;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/** Vision-description capability seam (`ctx.visionDescribe`). @module @phamkhachoabk/dsh-vision-describe */
|
|
2
|
+
import { Context, Service } from '@deepseek-ai/cordis';
|
|
3
|
+
import z from '@deepseek-ai/schemastery';
|
|
4
|
+
import { VisionDescribeEngine } from "./engine.js";
|
|
5
|
+
export { VisionDescribeEngine } from "./engine.js";
|
|
6
|
+
export * from "./types.js";
|
|
7
|
+
/** Default prompt asked of every engine, steering it toward what a text-only model needs. */
|
|
8
|
+
const DEFAULT_PROMPT = 'Describe this image for someone who cannot see it. Cover what kind of '
|
|
9
|
+
+ 'image it is (photo, screenshot, diagram, UI, chart, document…), its overall layout, and any '
|
|
10
|
+
+ 'visually significant detail a caption would otherwise miss — colors, icons, highlighted or '
|
|
11
|
+
+ 'error states, chart shapes, spatial relationships. Do not transcribe long passages of text '
|
|
12
|
+
+ 'verbatim; a separate OCR pass already does that.';
|
|
13
|
+
/**
|
|
14
|
+
* Registry and fallback pipeline for vision-description engines.
|
|
15
|
+
*
|
|
16
|
+
* A registry rather than an abstract seam because the capability has several
|
|
17
|
+
* providers competing in a defined order, the same shape `ctx.ocr` uses.
|
|
18
|
+
*/
|
|
19
|
+
export class VisionDescribeService extends Service {
|
|
20
|
+
config;
|
|
21
|
+
/** Deployment defaults; the one place request defaulting is configured. */
|
|
22
|
+
static Config = z.object({
|
|
23
|
+
perImageTimeoutMs: z.number().step(1).min(100).default(60_000)
|
|
24
|
+
.description('Budget one engine gets for one image.'),
|
|
25
|
+
prompt: z.string().default(DEFAULT_PROMPT)
|
|
26
|
+
.description('Instruction sent to every engine, steering what it attends to.'),
|
|
27
|
+
});
|
|
28
|
+
registrations = new Map();
|
|
29
|
+
constructor(ctx, config) {
|
|
30
|
+
super(ctx, 'visionDescribe');
|
|
31
|
+
this.config = config;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Add one engine to the ladder.
|
|
35
|
+
* @param engine - the provider's engine instance.
|
|
36
|
+
* @returns an idempotent disposer that removes exactly this registration.
|
|
37
|
+
*/
|
|
38
|
+
registerEngine(engine) {
|
|
39
|
+
if (this.registrations.has(engine.id)) {
|
|
40
|
+
throw new Error(`visionDescribe: engine ${engine.id} is already registered`);
|
|
41
|
+
}
|
|
42
|
+
const registration = { engine };
|
|
43
|
+
this.registrations.set(engine.id, registration);
|
|
44
|
+
return () => {
|
|
45
|
+
if (this.registrations.get(engine.id) === registration) {
|
|
46
|
+
this.registrations.delete(engine.id);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Identity of the whole registered ladder. It participates in cache keys, so
|
|
52
|
+
* adding, removing or upgrading any engine invalidates cached results with
|
|
53
|
+
* no explicit invalidation step.
|
|
54
|
+
* @returns a stable string over every registered engine and its version.
|
|
55
|
+
*/
|
|
56
|
+
identity() {
|
|
57
|
+
return this.engines().map(engine => `${engine.id}@${engine.version}`).join(',');
|
|
58
|
+
}
|
|
59
|
+
/** Registered engines in ladder order, most preferred first. */
|
|
60
|
+
engines() {
|
|
61
|
+
return [...this.registrations.values()]
|
|
62
|
+
.map(registration => registration.engine)
|
|
63
|
+
.sort((left, right) => left.tier - right.tier || left.id.localeCompare(right.id));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Turn a caller's wishes into a complete spec. Defaulting lives here, at the
|
|
67
|
+
* package boundary, so no engine ever sees a partially specified request.
|
|
68
|
+
* @param request - optional caller overrides.
|
|
69
|
+
* @returns a fully resolved spec.
|
|
70
|
+
*/
|
|
71
|
+
resolve(request = {}) {
|
|
72
|
+
return {
|
|
73
|
+
perImageTimeoutMs: request.perImageTimeoutMs ?? this.config.perImageTimeoutMs,
|
|
74
|
+
prompt: request.prompt ?? this.config.prompt,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** Warm every ready engine that offers it. Failures are advisory and logged. */
|
|
78
|
+
async warmUp(signal) {
|
|
79
|
+
await Promise.all(this.engines().map(async (engine) => {
|
|
80
|
+
if (engine.warmUp === undefined)
|
|
81
|
+
return;
|
|
82
|
+
const availability = await this.availabilityOf(engine, signal);
|
|
83
|
+
if (availability.kind !== 'ready')
|
|
84
|
+
return;
|
|
85
|
+
try {
|
|
86
|
+
await engine.warmUp(signal);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
this.ctx.logger.warn(`visionDescribe: warm-up of ${engine.id} failed: ${String(error)}`);
|
|
90
|
+
}
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
availabilityOf(engine, signal) {
|
|
94
|
+
const registration = this.registrations.get(engine.id);
|
|
95
|
+
if (registration === undefined) {
|
|
96
|
+
return Promise.resolve({ kind: 'unusable', reason: 'engine is not registered' });
|
|
97
|
+
}
|
|
98
|
+
registration.availability ??= engine.probe(signal).catch((error) => ({
|
|
99
|
+
kind: 'unusable',
|
|
100
|
+
reason: String(error),
|
|
101
|
+
}));
|
|
102
|
+
return registration.availability;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Describe every image, advancing through the engine ladder until one
|
|
106
|
+
* succeeds. Never throws for a describe failure: an image no engine could
|
|
107
|
+
* describe comes back as a `failed` result so the caller can carry on.
|
|
108
|
+
* @param inputs - images to describe.
|
|
109
|
+
* @param spec - a resolved spec from {@link resolve}.
|
|
110
|
+
* @param signal - cancellation for the whole call.
|
|
111
|
+
* @returns one result per input, in input order.
|
|
112
|
+
*/
|
|
113
|
+
async describe(inputs, spec, signal) {
|
|
114
|
+
const started = Date.now();
|
|
115
|
+
const pending = new Map(inputs.map(input => [input.attachmentId, { input, records: [] }]));
|
|
116
|
+
for (const engine of this.engines()) {
|
|
117
|
+
const remaining = [...pending.values()].filter(entry => entry.accepted === undefined);
|
|
118
|
+
if (remaining.length === 0 || signal.aborted)
|
|
119
|
+
break;
|
|
120
|
+
const availability = await this.availabilityOf(engine, signal);
|
|
121
|
+
if (availability.kind !== 'ready') {
|
|
122
|
+
for (const entry of remaining) {
|
|
123
|
+
entry.records.push({
|
|
124
|
+
engine: engine.id, outcome: 'skipped', ms: 0, detail: availability.reason,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
let attempts;
|
|
130
|
+
try {
|
|
131
|
+
attempts = await engine.describe(remaining.map(entry => entry.input), spec, signal);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
// An engine that throws is a broken engine, not a failed image; the
|
|
135
|
+
// ladder continues so one bad provider cannot deny the whole feature.
|
|
136
|
+
for (const entry of remaining) {
|
|
137
|
+
entry.records.push({
|
|
138
|
+
engine: engine.id, outcome: 'error', ms: 0, detail: String(error),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
for (const attempt of attempts) {
|
|
144
|
+
const entry = pending.get(attempt.attachmentId);
|
|
145
|
+
if (entry === undefined)
|
|
146
|
+
continue;
|
|
147
|
+
entry.records.push({
|
|
148
|
+
engine: engine.id,
|
|
149
|
+
outcome: attempt.outcome,
|
|
150
|
+
ms: attempt.ms,
|
|
151
|
+
...(attempt.detail === undefined ? {} : { detail: attempt.detail }),
|
|
152
|
+
});
|
|
153
|
+
if (attempt.outcome === 'ok') {
|
|
154
|
+
entry.accepted = attempt;
|
|
155
|
+
entry.acceptedEngine = engine.id;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
const totalMs = Date.now() - started;
|
|
160
|
+
return inputs.map(input => this.finish(pending.get(input.attachmentId), totalMs));
|
|
161
|
+
}
|
|
162
|
+
finish(entry, totalMs) {
|
|
163
|
+
const attempted = entry.records;
|
|
164
|
+
const accepted = entry.accepted;
|
|
165
|
+
if (accepted === undefined || entry.acceptedEngine === undefined) {
|
|
166
|
+
return {
|
|
167
|
+
attachmentId: entry.input.attachmentId,
|
|
168
|
+
status: 'failed',
|
|
169
|
+
engine: '',
|
|
170
|
+
attemptedEngines: attempted,
|
|
171
|
+
fallback: 'exhausted',
|
|
172
|
+
description: '',
|
|
173
|
+
timings: { totalMs, engineMs: attempted.reduce((sum, record) => sum + record.ms, 0) },
|
|
174
|
+
warnings: [],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
// `none` only when the very first engine tried carried the result.
|
|
178
|
+
const fallback = attempted[0]?.engine === entry.acceptedEngine ? 'none' : 'used';
|
|
179
|
+
return {
|
|
180
|
+
attachmentId: entry.input.attachmentId,
|
|
181
|
+
status: 'ok',
|
|
182
|
+
engine: entry.acceptedEngine,
|
|
183
|
+
attemptedEngines: attempted,
|
|
184
|
+
fallback,
|
|
185
|
+
description: accepted.description,
|
|
186
|
+
timings: { totalMs, engineMs: accepted.ms },
|
|
187
|
+
warnings: accepted.warnings,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
export default VisionDescribeService;
|
package/lib/schema.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Durable-boundary validation for cached results. @module @phamkhachoabk/dsh-vision-describe */
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import type { VisionDescribeResult } from './types.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Validates every cached record. The cache is a durable boundary, so a record
|
|
6
|
+
* written by an older build is rejected rather than trusted.
|
|
7
|
+
*/
|
|
8
|
+
export declare const visionDescribeResultSchema: z.ZodType<VisionDescribeResult>;
|
package/lib/schema.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Durable-boundary validation for cached results. @module @phamkhachoabk/dsh-vision-describe */
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
const outcome = z.enum(['ok', 'empty', 'error', 'timeout', 'skipped']);
|
|
4
|
+
/**
|
|
5
|
+
* Validates every cached record. The cache is a durable boundary, so a record
|
|
6
|
+
* written by an older build is rejected rather than trusted.
|
|
7
|
+
*/
|
|
8
|
+
export const visionDescribeResultSchema = z.object({
|
|
9
|
+
attachmentId: z.string(),
|
|
10
|
+
status: z.enum(['ok', 'failed']),
|
|
11
|
+
engine: z.string(),
|
|
12
|
+
attemptedEngines: z.array(z.object({
|
|
13
|
+
engine: z.string(),
|
|
14
|
+
outcome,
|
|
15
|
+
ms: z.number(),
|
|
16
|
+
detail: z.string().optional(),
|
|
17
|
+
})),
|
|
18
|
+
fallback: z.enum(['none', 'used', 'exhausted']),
|
|
19
|
+
description: z.string(),
|
|
20
|
+
timings: z.object({ totalMs: z.number(), engineMs: z.number() }),
|
|
21
|
+
warnings: z.array(z.string()),
|
|
22
|
+
});
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/** Types of the vision-description capability seam. @module @phamkhachoabk/dsh-vision-describe */
|
|
2
|
+
import type { AttachmentId } from '@deepseek-ai/dsh-attachment';
|
|
3
|
+
/** Why one engine's attempt on one image ended. */
|
|
4
|
+
export type VisionDescribeOutcome = 'ok' | 'empty' | 'error' | 'timeout' | 'skipped';
|
|
5
|
+
export interface VisionDescribeAttemptRecord {
|
|
6
|
+
engine: string;
|
|
7
|
+
outcome: VisionDescribeOutcome;
|
|
8
|
+
ms: number;
|
|
9
|
+
/** Present for `error` and `timeout`; the pipeline surfaces it in warnings. */
|
|
10
|
+
detail?: string;
|
|
11
|
+
}
|
|
12
|
+
/** One engine's answer for one image, before the pipeline decides to accept it. */
|
|
13
|
+
export interface VisionDescribeAttempt {
|
|
14
|
+
attachmentId: AttachmentId;
|
|
15
|
+
outcome: VisionDescribeOutcome;
|
|
16
|
+
ms: number;
|
|
17
|
+
/** Free-form natural-language account of what the image shows. */
|
|
18
|
+
description: string;
|
|
19
|
+
warnings: readonly string[];
|
|
20
|
+
detail?: string;
|
|
21
|
+
}
|
|
22
|
+
export type VisionDescribeStatus = 'ok' | 'failed';
|
|
23
|
+
export type VisionDescribeFallback = 'none' | 'used' | 'exhausted';
|
|
24
|
+
/** The pipeline's answer for one image. */
|
|
25
|
+
export interface VisionDescribeResult {
|
|
26
|
+
attachmentId: AttachmentId;
|
|
27
|
+
status: VisionDescribeStatus;
|
|
28
|
+
/** Engine that produced `description`; empty when every engine failed. */
|
|
29
|
+
engine: string;
|
|
30
|
+
attemptedEngines: readonly VisionDescribeAttemptRecord[];
|
|
31
|
+
fallback: VisionDescribeFallback;
|
|
32
|
+
description: string;
|
|
33
|
+
timings: {
|
|
34
|
+
totalMs: number;
|
|
35
|
+
engineMs: number;
|
|
36
|
+
};
|
|
37
|
+
warnings: readonly string[];
|
|
38
|
+
}
|
|
39
|
+
/** One image handed to the pipeline. */
|
|
40
|
+
export interface VisionDescribeInput {
|
|
41
|
+
attachmentId: AttachmentId;
|
|
42
|
+
/** Absolute host path readable by the provider's execution world. */
|
|
43
|
+
path: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* A caller's wishes, every field optional. Turning this into a
|
|
47
|
+
* {@link VisionDescribeSpec} is an explicit `resolve()` step at the package
|
|
48
|
+
* boundary, never a hidden `??` inside the pipeline.
|
|
49
|
+
*/
|
|
50
|
+
export interface VisionDescribeRequest {
|
|
51
|
+
perImageTimeoutMs?: number;
|
|
52
|
+
/** Instruction steering what the engine attends to; engines apply their own default. */
|
|
53
|
+
prompt?: string;
|
|
54
|
+
}
|
|
55
|
+
/** A fully resolved request. Engines receive only this. */
|
|
56
|
+
export interface VisionDescribeSpec {
|
|
57
|
+
perImageTimeoutMs: number;
|
|
58
|
+
prompt: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Availability of one engine. A missing runtime and an unsupported platform
|
|
62
|
+
* are deliberately indistinguishable, so consumers have one fail-closed path.
|
|
63
|
+
*/
|
|
64
|
+
export type EngineAvailability = {
|
|
65
|
+
kind: 'ready';
|
|
66
|
+
} | {
|
|
67
|
+
kind: 'unusable';
|
|
68
|
+
reason: string;
|
|
69
|
+
};
|
package/lib/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@phamkhachoabk/dsh-vision-describe",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Vision description capability seam for DeepSeek Harness: ctx.visionDescribe, the engine contract, and the fallback pipeline for whole-image semantic understanding",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./schema": {
|
|
14
|
+
"types": "./lib/schema.d.ts",
|
|
15
|
+
"default": "./lib/schema.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"lib/",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"zod": "^4.4.3"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
32
|
+
"@deepseek-ai/dsh-attachment": "^0.1.5-rc.1",
|
|
33
|
+
"@deepseek-ai/schemastery": "^3.18.2"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
37
|
+
"@deepseek-ai/dsh-attachment": "^0.1.5-rc.1",
|
|
38
|
+
"@deepseek-ai/schemastery": "^3.18.2"
|
|
39
|
+
}
|
|
40
|
+
}
|