@tangle-network/agent-app 0.43.66 → 0.43.68
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/assistant/index.d.ts +4 -2
- package/dist/assistant/index.js +6 -3
- package/dist/assistant/index.js.map +1 -1
- package/dist/{attachment-validation-B2FFna9E.d.ts → attachment-validation-Dv1A_Puy.d.ts} +1 -1
- package/dist/chat-routes/index.d.ts +4 -3
- package/dist/chat-routes/index.js +6 -4
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chat-store/index.d.ts +3 -2
- package/dist/chat-store/index.js +4 -1
- package/dist/chat-store/index.js.map +1 -1
- package/dist/{chunk-JWBZ74TW.js → chunk-7ESQUSAC.js} +5 -3
- package/dist/{chunk-JWBZ74TW.js.map → chunk-7ESQUSAC.js.map} +1 -1
- package/dist/{chunk-X3N2H6JE.js → chunk-AFNTRJQ7.js} +10 -1
- package/dist/chunk-AFNTRJQ7.js.map +1 -0
- package/dist/chunk-F2CBC4DY.js +193 -0
- package/dist/chunk-F2CBC4DY.js.map +1 -0
- package/dist/chunk-HRH7ASAG.js +759 -0
- package/dist/chunk-HRH7ASAG.js.map +1 -0
- package/dist/{chunk-YTSEDJWA.js → chunk-RXOTWZ4G.js} +22 -6
- package/dist/chunk-RXOTWZ4G.js.map +1 -0
- package/dist/chunk-UDSY2F6N.js +331 -0
- package/dist/chunk-UDSY2F6N.js.map +1 -0
- package/dist/chunk-UOAYS72M.js +80 -0
- package/dist/chunk-UOAYS72M.js.map +1 -0
- package/dist/chunk-UP33Z633.js +141 -0
- package/dist/chunk-UP33Z633.js.map +1 -0
- package/dist/{chunk-FMDMI25K.js → chunk-V55WJSR4.js} +2 -2
- package/dist/{chunk-YKBDH2UY.js → chunk-WL7XHLDK.js} +2 -2
- package/dist/{chunk-7CTIUCQ4.js → chunk-YEFFHORB.js} +2 -73
- package/dist/chunk-YEFFHORB.js.map +1 -0
- package/dist/eval-campaign/index.d.ts +2 -81
- package/dist/index.d.ts +5 -1
- package/dist/index.js +62 -8
- package/dist/model-resolution/index.d.ts +100 -1
- package/dist/model-resolution/index.js +108 -0
- package/dist/model-resolution/index.js.map +1 -1
- package/dist/{parts-Bg8qcDvB.d.ts → parts-2ymE5cs-.d.ts} +15 -2
- package/dist/queue-C24V13h9.d.ts +68 -0
- package/dist/runtime/index.js +3 -2
- package/dist/sandbox/index.js +3 -2
- package/dist/teams/index.js +5 -5
- package/dist/teams/invitations-api.js +4 -4
- package/dist/teams-react/index.js +3 -3
- package/dist/tools/index.js +8 -6
- package/dist/trust-gate-Dcm5xSva.d.ts +83 -0
- package/dist/types-CEchbvgz.d.ts +268 -0
- package/dist/web-react/index.d.ts +97 -5
- package/dist/web-react/index.js +31 -4
- package/dist/work-product/index.d.ts +331 -0
- package/dist/work-product/index.js +54 -0
- package/dist/work-product/index.js.map +1 -0
- package/dist/work-product-react/index.d.ts +36 -0
- package/dist/work-product-react/index.js +180 -0
- package/dist/work-product-react/index.js.map +1 -0
- package/package.json +15 -1
- package/dist/chunk-7CTIUCQ4.js.map +0 -1
- package/dist/chunk-X3N2H6JE.js.map +0 -1
- package/dist/chunk-YTSEDJWA.js.map +0 -1
- /package/dist/{chunk-FMDMI25K.js.map → chunk-V55WJSR4.js.map} +0 -0
- /package/dist/{chunk-YKBDH2UY.js.map → chunk-WL7XHLDK.js.map} +0 -0
|
@@ -1,3 +1,105 @@
|
|
|
1
|
+
// src/model-resolution/failover.ts
|
|
2
|
+
var UPSTREAM_UNAVAILABLE_CODES = [
|
|
3
|
+
"provider_inference_unavailable",
|
|
4
|
+
"upstream_unavailable",
|
|
5
|
+
"insufficient_quota",
|
|
6
|
+
"model_not_available",
|
|
7
|
+
"server_error",
|
|
8
|
+
"bad_gateway",
|
|
9
|
+
"service_unavailable"
|
|
10
|
+
];
|
|
11
|
+
var UPSTREAM_UNAVAILABLE_STATUSES = [429, 500, 502, 503, 504];
|
|
12
|
+
var UPSTREAM_UNAVAILABLE_MESSAGES = [
|
|
13
|
+
"bad gateway",
|
|
14
|
+
"service unavailable",
|
|
15
|
+
"inference temporarily unavailable",
|
|
16
|
+
"provider inference is unavailable",
|
|
17
|
+
"insufficient balance",
|
|
18
|
+
"usage limits",
|
|
19
|
+
"quota exceeded",
|
|
20
|
+
"rate limit",
|
|
21
|
+
"overloaded",
|
|
22
|
+
"temporarily unavailable"
|
|
23
|
+
];
|
|
24
|
+
function readString(source, key) {
|
|
25
|
+
const value = source[key];
|
|
26
|
+
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
27
|
+
}
|
|
28
|
+
function isUpstreamUnavailable(signal) {
|
|
29
|
+
if (signal === null || typeof signal !== "object") return false;
|
|
30
|
+
const record = signal;
|
|
31
|
+
if (record.success === true) return false;
|
|
32
|
+
const nested = record.error;
|
|
33
|
+
const nestedRecord = nested !== null && typeof nested === "object" ? nested : void 0;
|
|
34
|
+
const code = readString(record, "errorCode") ?? readString(record, "code") ?? (nestedRecord ? readString(nestedRecord, "code") ?? readString(nestedRecord, "type") : void 0);
|
|
35
|
+
if (code && UPSTREAM_UNAVAILABLE_CODES.includes(code)) return true;
|
|
36
|
+
for (const key of ["status", "statusCode", "httpStatus"]) {
|
|
37
|
+
const value = record[key];
|
|
38
|
+
if (typeof value === "number" && UPSTREAM_UNAVAILABLE_STATUSES.includes(value)) return true;
|
|
39
|
+
}
|
|
40
|
+
const message = readString(record, "message") ?? readString(record, "error") ?? (nestedRecord ? readString(nestedRecord, "message") : void 0);
|
|
41
|
+
if (!message) return false;
|
|
42
|
+
const lowered = message.toLowerCase();
|
|
43
|
+
return UPSTREAM_UNAVAILABLE_MESSAGES.some((fragment) => lowered.includes(fragment));
|
|
44
|
+
}
|
|
45
|
+
var ModelFailoverExhaustedError = class extends Error {
|
|
46
|
+
attempts;
|
|
47
|
+
constructor(attempts) {
|
|
48
|
+
const trail = attempts.map((a) => `${a.model}: ${a.reason ?? "failed"}`).join(" | ");
|
|
49
|
+
super(`All ${attempts.length} model(s) failed. ${trail}`);
|
|
50
|
+
this.name = "ModelFailoverExhaustedError";
|
|
51
|
+
this.attempts = attempts;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
function describe(signal) {
|
|
55
|
+
if (signal instanceof Error) return signal.message;
|
|
56
|
+
if (signal !== null && typeof signal === "object") {
|
|
57
|
+
const record = signal;
|
|
58
|
+
const message = readString(record, "error") ?? readString(record, "message") ?? readString(record, "errorCode");
|
|
59
|
+
if (message) return message;
|
|
60
|
+
}
|
|
61
|
+
return String(signal);
|
|
62
|
+
}
|
|
63
|
+
async function runWithModelFailover(input) {
|
|
64
|
+
const models = input.models.map((m) => m.trim()).filter((m) => m.length > 0);
|
|
65
|
+
if (models.length === 0) throw new Error("runWithModelFailover requires at least one model");
|
|
66
|
+
const isUnavailableResult = input.isUnavailableResult ?? ((r) => isUpstreamUnavailable(r));
|
|
67
|
+
const isUnavailableError = input.isUnavailableError ?? isUpstreamUnavailable;
|
|
68
|
+
const attempts = [];
|
|
69
|
+
for (let index = 0; index < models.length; index += 1) {
|
|
70
|
+
const model = models[index];
|
|
71
|
+
let result;
|
|
72
|
+
try {
|
|
73
|
+
result = await input.run(model);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (!isUnavailableError(error)) throw error;
|
|
76
|
+
const attempt = { model, ok: false, reason: describe(error) };
|
|
77
|
+
attempts.push(attempt);
|
|
78
|
+
const next = models[index + 1];
|
|
79
|
+
if (next) input.onFallback?.(attempt, next);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (isUnavailableResult(result)) {
|
|
83
|
+
const attempt = { model, ok: false, reason: describe(result) };
|
|
84
|
+
attempts.push(attempt);
|
|
85
|
+
const next = models[index + 1];
|
|
86
|
+
if (next) input.onFallback?.(attempt, next);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
attempts.push({ model, ok: true });
|
|
90
|
+
return { value: result, model, attempts, usedFallback: index > 0 };
|
|
91
|
+
}
|
|
92
|
+
throw new ModelFailoverExhaustedError(attempts);
|
|
93
|
+
}
|
|
94
|
+
function buildModelChain(preferred, fallbacks) {
|
|
95
|
+
const chain = [];
|
|
96
|
+
for (const model of [preferred, ...fallbacks]) {
|
|
97
|
+
const cleaned = typeof model === "string" ? model.trim() : "";
|
|
98
|
+
if (cleaned.length > 0 && !chain.includes(cleaned)) chain.push(cleaned);
|
|
99
|
+
}
|
|
100
|
+
return chain;
|
|
101
|
+
}
|
|
102
|
+
|
|
1
103
|
// src/model-resolution/index.ts
|
|
2
104
|
function canonicalModelId(model) {
|
|
3
105
|
if (model.id.includes("/")) return model.id;
|
|
@@ -67,10 +169,16 @@ function catalogIdsForModel(model) {
|
|
|
67
169
|
return [...ids];
|
|
68
170
|
}
|
|
69
171
|
export {
|
|
172
|
+
ModelFailoverExhaustedError,
|
|
173
|
+
UPSTREAM_UNAVAILABLE_CODES,
|
|
174
|
+
UPSTREAM_UNAVAILABLE_STATUSES,
|
|
175
|
+
buildModelChain,
|
|
70
176
|
catalogIdsForModel,
|
|
71
177
|
cleanModelId,
|
|
178
|
+
isUpstreamUnavailable,
|
|
72
179
|
isWellFormedModelId,
|
|
73
180
|
resolveChatModel,
|
|
181
|
+
runWithModelFailover,
|
|
74
182
|
validateChatModelId
|
|
75
183
|
};
|
|
76
184
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/model-resolution/index.ts"],"sourcesContent":["/**\n * Canonical chat-model resolution — identical across every agent app.\n *\n * The ONLY per-app inputs are DATA, never logic: the default model, the\n * allowlist, the env value the deployment set, and the catalog-fetch loader.\n * The logic is one precedence ladder + one fail-closed validator that every\n * product uses the same way — there is no per-product variant, no env-var name\n * baked in, and no backend dimension (router-vs-sandbox is the harness/dispatch\n * concern, not model resolution; a sandbox's provider default lives in the\n * sandbox subpath).\n *\n * - resolveChatModel: request > workspace > env > default. The product reads its\n * own deploy env var and passes the VALUE as `envModel`; the shell knows no\n * env-var names. Source is canonical: 'request' | 'workspace' | 'env' | 'default'.\n * - validateChatModelId: fail-closed. Admit an id that is in the allowlist, or\n * equals the operator-set env model, or is served by the live router catalog\n * (exact, or a bare id resolved to its canonical id when the suffix is unique).\n */\n\n/** The router /v1/models entry shape this module reads. Minimal on purpose. */\nexport interface ModelInfo {\n id: string\n name?: string\n _provider?: string\n provider?: string\n}\n\n/** Canonical (provider-prefixed) id for a catalog entry: pass through an id that\n * already carries a provider, else prefix the entry's provider when present. */\nfunction canonicalModelId(model: ModelInfo): string {\n if (model.id.includes('/')) return model.id\n const provider = model._provider ?? model.provider\n return provider ? `${provider}/${model.id}` : model.id\n}\n\n/** Define possible origins for the chat model configuration values */\nexport type ChatModelSource = 'request' | 'workspace' | 'env' | 'default'\n\n/** Resolve a chat model with its identifier and source information */\nexport interface ResolvedChatModel {\n model: string\n source: ChatModelSource\n}\n\n/** Represent successful chat model validation with a true status and a validated string value */\nexport interface ChatModelValidationSuccess {\n succeeded: true\n value: string\n}\n\n/** Describe a failed chat model validation result with an error message */\nexport interface ChatModelValidationFailure {\n succeeded: false\n error: string\n}\n\n/** Resolve the outcome of validating a chat model as either success or failure */\nexport type ChatModelValidationResult = ChatModelValidationSuccess | ChatModelValidationFailure\n\n/** The catalog-fetch boundary: maps a router base URL to the raw model list. */\nexport type LoadModels = (routerBaseUrl: string) => Promise<ModelInfo[]>\n\n/** Resolve the effective chat model input by prioritizing request, workspace, environment, and default models */\nexport interface ResolveChatModelInput {\n /** Per-request override (highest precedence). */\n requestModel?: string\n /** Persisted workspace-pinned model. */\n workspaceModel?: string\n /** The value the deployment's model env var holds (the product reads its own\n * var name and passes the value — the shell stays env-var-name agnostic). */\n envModel?: string\n /** Final fallback (the product's default, typically profile.model.default). */\n defaultModel: string\n}\n\n/** Resolve the chat-turn model by the one canonical precedence. Blank values are\n * treated as absent. */\nexport function resolveChatModel(input: ResolveChatModelInput): ResolvedChatModel {\n const request = cleanModelId(input.requestModel)\n if (request) return { model: request, source: 'request' }\n const workspace = cleanModelId(input.workspaceModel)\n if (workspace) return { model: workspace, source: 'workspace' }\n const env = cleanModelId(input.envModel)\n if (env) return { model: env, source: 'env' }\n return { model: input.defaultModel, source: 'default' }\n}\n\n/** Define input parameters for validating chat model IDs with optional allowlist and catalog access details */\nexport interface ValidateChatModelIdInput {\n /** Ids accepted without a catalog round-trip (defaults + operator-trusted). */\n allowlist?: Iterable<string>\n /** The operator-set env model value — always admitted (operator-trusted). */\n envModel?: string\n /** Catalog loader; required to reach the catalog path. */\n loadModels?: LoadModels\n /** Catalog endpoint base; required to reach the catalog path. */\n routerBaseUrl?: string\n}\n\n/**\n * Fail-closed model-id validation. Accepts an id only when it is well-formed AND\n * (in the allowlist, or equals the operator-set env model, or served by the live\n * catalog). A bare id (no provider prefix) resolves to its canonical id only when\n * the suffix is unique across the catalog — an ambiguous suffix is rejected\n * rather than silently assigned a provider.\n */\nexport async function validateChatModelId(\n modelId: unknown,\n input: ValidateChatModelIdInput,\n): Promise<ChatModelValidationResult> {\n const cleaned = cleanModelId(modelId)\n if (!cleaned) return { succeeded: false, error: 'Model id must be a non-empty string.' }\n if (!isWellFormedModelId(cleaned)) return { succeeded: false, error: `Model id is malformed: ${cleaned}` }\n\n const allowed = new Set(input.allowlist ?? [])\n if (allowed.has(cleaned)) return { succeeded: true, value: cleaned }\n\n // The operator-set env model is trusted without a catalog round-trip.\n if (cleanModelId(input.envModel) === cleaned) return { succeeded: true, value: cleaned }\n\n if (!input.loadModels || typeof input.routerBaseUrl !== 'string' || input.routerBaseUrl.length === 0) {\n return { succeeded: false, error: `Model is not available: ${cleaned}` }\n }\n\n let catalog: ModelInfo[]\n try {\n catalog = await input.loadModels(input.routerBaseUrl)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n return { succeeded: false, error: `Could not validate model catalog: ${message}` }\n }\n\n const ids = new Set(catalog.flatMap(catalogIdsForModel))\n if (ids.has(cleaned)) return { succeeded: true, value: cleaned }\n\n if (!cleaned.includes('/')) {\n const canonicalBySuffix = new Map<string, string[]>()\n for (const model of catalog) {\n if (typeof model.id !== 'string' || !model.id.trim()) continue\n const canonical = canonicalModelId(model)\n if (!canonical.includes('/')) continue\n const suffix = canonical.split('/').slice(1).join('/')\n const entries = canonicalBySuffix.get(suffix)\n if (entries) entries.push(canonical)\n else canonicalBySuffix.set(suffix, [canonical])\n }\n const matches = canonicalBySuffix.get(cleaned)\n if (matches && matches.length === 1) return { succeeded: true, value: matches[0]! }\n }\n\n return { succeeded: false, error: `Model is not available: ${cleaned}` }\n}\n\n/** Resolve and return a trimmed string model ID or undefined for invalid or empty input */\nexport function cleanModelId(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\n/** Validate if a model ID string conforms to length and character format requirements */\nexport function isWellFormedModelId(modelId: string): boolean {\n if (modelId.length > 200) return false\n return /^[A-Za-z0-9._/@:-]+$/.test(modelId)\n}\n\n/** Resolve unique catalog IDs associated with a given model including its canonical form if applicable */\nexport function catalogIdsForModel(model: ModelInfo): string[] {\n const ids = new Set<string>()\n if (typeof model.id === 'string' && model.id.trim()) ids.add(model.id.trim())\n if (typeof model.id === 'string' && model.id.trim() && !model.id.includes('/')) {\n const canonical = canonicalModelId(model)\n if (canonical.includes('/')) ids.add(canonical)\n }\n return [...ids]\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/model-resolution/failover.ts","../../src/model-resolution/index.ts"],"sourcesContent":["/**\n * Model failover — the answer to \"one upstream hit a quota wall and the customer\n * got a Bad Gateway even though the router had abundant healthy capacity\".\n *\n * This is deliberately NOT a health probe. Probing before every turn buys a\n * round-trip of latency on the happy path and still races the outage (a model\n * healthy at probe time can 502 a second later). Failover is REACTIVE: run the\n * preferred model, and on an upstream-outage signal move to the next model in\n * the chain. Zero added latency when the preferred model works.\n *\n * Two facts drive the design, both measured against a live box during the\n * 2026-07-25 Anthropic/DeepSeek outage:\n *\n * 1. An outage is NOT always a thrown error. The sandbox resolves with a\n * payload — `{ success: false, errorCode: 'provider_inference_unavailable' }`\n * — so a classifier that only inspects `catch` misses the customer-visible\n * case entirely. `isUpstreamUnavailable` inspects resolved values too.\n * 2. Catalog membership is NOT liveness. The router's `/v1/models` still listed\n * every dead model during the outage, so `validateChatModelId` admitted\n * `claude-sonnet-4-6` while every call to it returned 502. Nothing upstream\n * of the actual call can be trusted to tell you a model is serving.\n *\n * Substrate-free: the caller injects `run`, so this composes with a sandbox\n * turn, a router chat completion, or a test fake without importing any of them.\n */\n\n/**\n * Error codes that mean \"this model's upstream is unavailable — a different\n * model may still work\". Deliberately excludes codes that would fail identically\n * on every model (bad request, auth, content filter): retrying those down a\n * chain burns latency and money to reach the same failure.\n */\nexport const UPSTREAM_UNAVAILABLE_CODES: readonly string[] = [\n 'provider_inference_unavailable',\n 'upstream_unavailable',\n 'insufficient_quota',\n 'model_not_available',\n 'server_error',\n 'bad_gateway',\n 'service_unavailable',\n]\n\n/** HTTP statuses that indicate an upstream capacity/availability problem. */\nexport const UPSTREAM_UNAVAILABLE_STATUSES: readonly number[] = [429, 500, 502, 503, 504]\n\n/**\n * Message fragments emitted by real upstreams during this class of outage.\n * Matched case-insensitively as a last resort, after code and status.\n */\nconst UPSTREAM_UNAVAILABLE_MESSAGES: readonly string[] = [\n 'bad gateway',\n 'service unavailable',\n 'inference temporarily unavailable',\n 'provider inference is unavailable',\n 'insufficient balance',\n 'usage limits',\n 'quota exceeded',\n 'rate limit',\n 'overloaded',\n 'temporarily unavailable',\n]\n\nfunction readString(source: Record<string, unknown>, key: string): string | undefined {\n const value = source[key]\n return typeof value === 'string' && value.trim().length > 0 ? value : undefined\n}\n\n/**\n * True when `signal` — a thrown error OR a resolved result payload — indicates\n * the model's upstream is unavailable and another model is worth trying.\n *\n * Checked in order of decreasing confidence: explicit code, HTTP status, then\n * message text. A resolved payload only counts as a failure when it carries an\n * explicit failure marker (`success: false`, or an `error`/`errorCode` field) —\n * a successful result is never misread as an outage.\n */\nexport function isUpstreamUnavailable(signal: unknown): boolean {\n if (signal === null || typeof signal !== 'object') return false\n const record = signal as Record<string, unknown>\n\n // A resolved payload that explicitly reports success is never an outage.\n if (record.success === true) return false\n\n const nested = record.error\n const nestedRecord = nested !== null && typeof nested === 'object' ? (nested as Record<string, unknown>) : undefined\n\n const code =\n readString(record, 'errorCode') ??\n readString(record, 'code') ??\n (nestedRecord ? (readString(nestedRecord, 'code') ?? readString(nestedRecord, 'type')) : undefined)\n if (code && UPSTREAM_UNAVAILABLE_CODES.includes(code)) return true\n\n for (const key of ['status', 'statusCode', 'httpStatus']) {\n const value = record[key]\n if (typeof value === 'number' && UPSTREAM_UNAVAILABLE_STATUSES.includes(value)) return true\n }\n\n const message =\n readString(record, 'message') ??\n readString(record, 'error') ??\n (nestedRecord ? readString(nestedRecord, 'message') : undefined)\n if (!message) return false\n const lowered = message.toLowerCase()\n return UPSTREAM_UNAVAILABLE_MESSAGES.some((fragment) => lowered.includes(fragment))\n}\n\n/** One model tried, and how it went. */\nexport interface ModelFailoverAttempt {\n model: string\n ok: boolean\n /** Why this model was abandoned. Absent when `ok`. */\n reason?: string\n}\n\n/** The outcome of a failover run: the value plus the full attempt trail. */\nexport interface ModelFailoverResult<T> {\n value: T\n /** The model that actually produced `value`. */\n model: string\n attempts: ModelFailoverAttempt[]\n /** True when the preferred (first) model did not serve the request. */\n usedFallback: boolean\n}\n\n/** Every model in the chain failed; carries the trail for logging. */\nexport class ModelFailoverExhaustedError extends Error {\n readonly attempts: ModelFailoverAttempt[]\n constructor(attempts: ModelFailoverAttempt[]) {\n const trail = attempts.map((a) => `${a.model}: ${a.reason ?? 'failed'}`).join(' | ')\n super(`All ${attempts.length} model(s) failed. ${trail}`)\n this.name = 'ModelFailoverExhaustedError'\n this.attempts = attempts\n }\n}\n\n/** Inputs to {@link runWithModelFailover}. */\nexport interface RunWithModelFailoverInput<T> {\n /** Preferred model first, then fallbacks in descending preference. */\n models: readonly string[]\n /** Executes one turn with the given model. */\n run: (model: string) => Promise<T>\n /**\n * Classifies a resolved result as an upstream outage. Defaults to\n * {@link isUpstreamUnavailable}, which understands the sandbox's\n * `{ success: false, errorCode }` payload.\n */\n isUnavailableResult?: (result: T) => boolean\n /** Classifies a thrown error. Defaults to {@link isUpstreamUnavailable}. */\n isUnavailableError?: (error: unknown) => boolean\n /** Observability hook fired each time a model is abandoned. */\n onFallback?: (attempt: ModelFailoverAttempt, nextModel: string) => void\n}\n\nfunction describe(signal: unknown): string {\n if (signal instanceof Error) return signal.message\n if (signal !== null && typeof signal === 'object') {\n const record = signal as Record<string, unknown>\n const message = readString(record, 'error') ?? readString(record, 'message') ?? readString(record, 'errorCode')\n if (message) return message\n }\n return String(signal)\n}\n\n/**\n * Run `run` against the first model in `models` that does not report an upstream\n * outage, falling through the chain in order.\n *\n * A non-outage failure (bad request, auth, content filter) is re-thrown\n * immediately rather than retried down the chain — those fail identically on\n * every model, so walking the chain would only multiply latency and spend.\n *\n * @throws ModelFailoverExhaustedError when every model reports an outage.\n */\nexport async function runWithModelFailover<T>(\n input: RunWithModelFailoverInput<T>,\n): Promise<ModelFailoverResult<T>> {\n const models = input.models.map((m) => m.trim()).filter((m) => m.length > 0)\n if (models.length === 0) throw new Error('runWithModelFailover requires at least one model')\n\n const isUnavailableResult = input.isUnavailableResult ?? ((r: T) => isUpstreamUnavailable(r))\n const isUnavailableError = input.isUnavailableError ?? isUpstreamUnavailable\n const attempts: ModelFailoverAttempt[] = []\n\n for (let index = 0; index < models.length; index += 1) {\n const model = models[index]!\n let result: T\n try {\n result = await input.run(model)\n } catch (error) {\n if (!isUnavailableError(error)) throw error\n const attempt: ModelFailoverAttempt = { model, ok: false, reason: describe(error) }\n attempts.push(attempt)\n const next = models[index + 1]\n if (next) input.onFallback?.(attempt, next)\n continue\n }\n\n if (isUnavailableResult(result)) {\n const attempt: ModelFailoverAttempt = { model, ok: false, reason: describe(result) }\n attempts.push(attempt)\n const next = models[index + 1]\n if (next) input.onFallback?.(attempt, next)\n continue\n }\n\n attempts.push({ model, ok: true })\n return { value: result, model, attempts, usedFallback: index > 0 }\n }\n\n throw new ModelFailoverExhaustedError(attempts)\n}\n\n/**\n * Build a failover chain: the preferred model first, then `fallbacks`, with\n * duplicates removed so a model is never retried twice in one turn.\n */\nexport function buildModelChain(preferred: string, fallbacks: readonly string[]): string[] {\n const chain: string[] = []\n for (const model of [preferred, ...fallbacks]) {\n const cleaned = typeof model === 'string' ? model.trim() : ''\n if (cleaned.length > 0 && !chain.includes(cleaned)) chain.push(cleaned)\n }\n return chain\n}\n","/**\n * Canonical chat-model resolution — identical across every agent app.\n *\n * The ONLY per-app inputs are DATA, never logic: the default model, the\n * allowlist, the env value the deployment set, and the catalog-fetch loader.\n * The logic is one precedence ladder + one fail-closed validator that every\n * product uses the same way — there is no per-product variant, no env-var name\n * baked in, and no backend dimension (router-vs-sandbox is the harness/dispatch\n * concern, not model resolution; a sandbox's provider default lives in the\n * sandbox subpath).\n *\n * - resolveChatModel: request > workspace > env > default. The product reads its\n * own deploy env var and passes the VALUE as `envModel`; the shell knows no\n * env-var names. Source is canonical: 'request' | 'workspace' | 'env' | 'default'.\n * - validateChatModelId: fail-closed. Admit an id that is in the allowlist, or\n * equals the operator-set env model, or is served by the live router catalog\n * (exact, or a bare id resolved to its canonical id when the suffix is unique).\n */\n\n/** The router /v1/models entry shape this module reads. Minimal on purpose. */\nexport interface ModelInfo {\n id: string\n name?: string\n _provider?: string\n provider?: string\n}\n\n/** Canonical (provider-prefixed) id for a catalog entry: pass through an id that\n * already carries a provider, else prefix the entry's provider when present. */\nfunction canonicalModelId(model: ModelInfo): string {\n if (model.id.includes('/')) return model.id\n const provider = model._provider ?? model.provider\n return provider ? `${provider}/${model.id}` : model.id\n}\n\n/** Define possible origins for the chat model configuration values */\nexport type ChatModelSource = 'request' | 'workspace' | 'env' | 'default'\n\n/** Resolve a chat model with its identifier and source information */\nexport interface ResolvedChatModel {\n model: string\n source: ChatModelSource\n}\n\n/** Represent successful chat model validation with a true status and a validated string value */\nexport interface ChatModelValidationSuccess {\n succeeded: true\n value: string\n}\n\n/** Describe a failed chat model validation result with an error message */\nexport interface ChatModelValidationFailure {\n succeeded: false\n error: string\n}\n\n/** Resolve the outcome of validating a chat model as either success or failure */\nexport type ChatModelValidationResult = ChatModelValidationSuccess | ChatModelValidationFailure\n\n/** The catalog-fetch boundary: maps a router base URL to the raw model list. */\nexport type LoadModels = (routerBaseUrl: string) => Promise<ModelInfo[]>\n\n/** Resolve the effective chat model input by prioritizing request, workspace, environment, and default models */\nexport interface ResolveChatModelInput {\n /** Per-request override (highest precedence). */\n requestModel?: string\n /** Persisted workspace-pinned model. */\n workspaceModel?: string\n /** The value the deployment's model env var holds (the product reads its own\n * var name and passes the value — the shell stays env-var-name agnostic). */\n envModel?: string\n /** Final fallback (the product's default, typically profile.model.default). */\n defaultModel: string\n}\n\n/** Resolve the chat-turn model by the one canonical precedence. Blank values are\n * treated as absent. */\nexport function resolveChatModel(input: ResolveChatModelInput): ResolvedChatModel {\n const request = cleanModelId(input.requestModel)\n if (request) return { model: request, source: 'request' }\n const workspace = cleanModelId(input.workspaceModel)\n if (workspace) return { model: workspace, source: 'workspace' }\n const env = cleanModelId(input.envModel)\n if (env) return { model: env, source: 'env' }\n return { model: input.defaultModel, source: 'default' }\n}\n\n/** Define input parameters for validating chat model IDs with optional allowlist and catalog access details */\nexport interface ValidateChatModelIdInput {\n /** Ids accepted without a catalog round-trip (defaults + operator-trusted). */\n allowlist?: Iterable<string>\n /** The operator-set env model value — always admitted (operator-trusted). */\n envModel?: string\n /** Catalog loader; required to reach the catalog path. */\n loadModels?: LoadModels\n /** Catalog endpoint base; required to reach the catalog path. */\n routerBaseUrl?: string\n}\n\n/**\n * Fail-closed model-id validation. Accepts an id only when it is well-formed AND\n * (in the allowlist, or equals the operator-set env model, or served by the live\n * catalog). A bare id (no provider prefix) resolves to its canonical id only when\n * the suffix is unique across the catalog — an ambiguous suffix is rejected\n * rather than silently assigned a provider.\n */\nexport async function validateChatModelId(\n modelId: unknown,\n input: ValidateChatModelIdInput,\n): Promise<ChatModelValidationResult> {\n const cleaned = cleanModelId(modelId)\n if (!cleaned) return { succeeded: false, error: 'Model id must be a non-empty string.' }\n if (!isWellFormedModelId(cleaned)) return { succeeded: false, error: `Model id is malformed: ${cleaned}` }\n\n const allowed = new Set(input.allowlist ?? [])\n if (allowed.has(cleaned)) return { succeeded: true, value: cleaned }\n\n // The operator-set env model is trusted without a catalog round-trip.\n if (cleanModelId(input.envModel) === cleaned) return { succeeded: true, value: cleaned }\n\n if (!input.loadModels || typeof input.routerBaseUrl !== 'string' || input.routerBaseUrl.length === 0) {\n return { succeeded: false, error: `Model is not available: ${cleaned}` }\n }\n\n let catalog: ModelInfo[]\n try {\n catalog = await input.loadModels(input.routerBaseUrl)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n return { succeeded: false, error: `Could not validate model catalog: ${message}` }\n }\n\n const ids = new Set(catalog.flatMap(catalogIdsForModel))\n if (ids.has(cleaned)) return { succeeded: true, value: cleaned }\n\n if (!cleaned.includes('/')) {\n const canonicalBySuffix = new Map<string, string[]>()\n for (const model of catalog) {\n if (typeof model.id !== 'string' || !model.id.trim()) continue\n const canonical = canonicalModelId(model)\n if (!canonical.includes('/')) continue\n const suffix = canonical.split('/').slice(1).join('/')\n const entries = canonicalBySuffix.get(suffix)\n if (entries) entries.push(canonical)\n else canonicalBySuffix.set(suffix, [canonical])\n }\n const matches = canonicalBySuffix.get(cleaned)\n if (matches && matches.length === 1) return { succeeded: true, value: matches[0]! }\n }\n\n return { succeeded: false, error: `Model is not available: ${cleaned}` }\n}\n\n/** Resolve and return a trimmed string model ID or undefined for invalid or empty input */\nexport function cleanModelId(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\n/** Validate if a model ID string conforms to length and character format requirements */\nexport function isWellFormedModelId(modelId: string): boolean {\n if (modelId.length > 200) return false\n return /^[A-Za-z0-9._/@:-]+$/.test(modelId)\n}\n\n/** Resolve unique catalog IDs associated with a given model including its canonical form if applicable */\nexport function catalogIdsForModel(model: ModelInfo): string[] {\n const ids = new Set<string>()\n if (typeof model.id === 'string' && model.id.trim()) ids.add(model.id.trim())\n if (typeof model.id === 'string' && model.id.trim() && !model.id.includes('/')) {\n const canonical = canonicalModelId(model)\n if (canonical.includes('/')) ids.add(canonical)\n }\n return [...ids]\n}\n\n// Reactive failover for the case validation cannot catch: a model that IS in the\n// catalog but whose upstream is down. Catalog membership is not liveness.\nexport {\n isUpstreamUnavailable,\n runWithModelFailover,\n buildModelChain,\n ModelFailoverExhaustedError,\n UPSTREAM_UNAVAILABLE_CODES,\n UPSTREAM_UNAVAILABLE_STATUSES,\n type ModelFailoverAttempt,\n type ModelFailoverResult,\n type RunWithModelFailoverInput,\n} from './failover'\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Part } from '@tangle-network/agent-interface';
|
|
2
2
|
import { b as ChatInteractionField, c as ChatInteractionStatus, i as InteractionAnswers, N as NoticeKind } from './contract-B3h7peV3.js';
|
|
3
|
+
import { g as WorkProductPersistedPart } from './types-CEchbvgz.js';
|
|
3
4
|
import { ChatPlanPersistedPart } from './plans/index.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -300,6 +301,11 @@ declare function parseChatTurnParts(raw: unknown): ChatTurnFilePartInput[];
|
|
|
300
301
|
* and field shapes.
|
|
301
302
|
* - `plan`: the durable-plan projection in `/plans`, derived from the sandbox
|
|
302
303
|
* SDK's authoritative plan lifecycle.
|
|
304
|
+
* - `work_product`: the persisted work-product anchor card in
|
|
305
|
+
* `/work-product`'s contract (`workProductToPersistedPart` /
|
|
306
|
+
* `persistedPartToWorkProduct`) — SYSTEM-authored on the ready transition
|
|
307
|
+
* and updated on a reviewer verdict; no prompt teaches an agent to author
|
|
308
|
+
* one.
|
|
303
309
|
* - `mention`: an `@`-picked reference to a file that already lives in the
|
|
304
310
|
* workspace sandbox (`FileMention` in `/chat-routes`'s wire contract, plus
|
|
305
311
|
* the image/file discriminant). Neither transport lane produces it — the
|
|
@@ -439,6 +445,11 @@ interface ChatInteractionPart {
|
|
|
439
445
|
}
|
|
440
446
|
/** Resolve a chat plan part by aliasing it to the persisted chat plan part type */
|
|
441
447
|
type ChatPlanPart = ChatPlanPersistedPart;
|
|
448
|
+
/** Persisted work-product anchor card — byte-matches
|
|
449
|
+
* `workProductToPersistedPart` in `/work-product`'s contract. Written by the
|
|
450
|
+
* PLATFORM on the ready transition (and updated on a verdict); never
|
|
451
|
+
* authored by a prompt. */
|
|
452
|
+
type ChatWorkProductPart = WorkProductPersistedPart;
|
|
442
453
|
/** Persisted one-line transcript notice — byte-matches `noticePart` in
|
|
443
454
|
* `/web-react`'s chat-interactions contract. */
|
|
444
455
|
interface ChatNoticePart {
|
|
@@ -468,7 +479,7 @@ interface ChatMentionPart {
|
|
|
468
479
|
turnId?: string;
|
|
469
480
|
}
|
|
470
481
|
/** Represent parts of a chat message including text, reasoning, tools, files, images, subtasks, steps, interactions, notices, plans, and mentions */
|
|
471
|
-
type ChatMessagePart = ChatTextPart | ChatReasoningPart | ChatToolPart | ChatFilePart | ChatImagePart | ChatSubtaskPart | ChatStepStartPart | ChatStepFinishPart | ChatInteractionPart | ChatNoticePart | ChatPlanPart | ChatMentionPart;
|
|
482
|
+
type ChatMessagePart = ChatTextPart | ChatReasoningPart | ChatToolPart | ChatFilePart | ChatImagePart | ChatSubtaskPart | ChatStepStartPart | ChatStepFinishPart | ChatInteractionPart | ChatNoticePart | ChatPlanPart | ChatWorkProductPart | ChatMentionPart;
|
|
472
483
|
/** Every canonical harness wire-part kind must be storable — compile-time
|
|
473
484
|
* guarantee that a new agent-interface part kind cannot silently fall out of
|
|
474
485
|
* the persisted vocabulary. */
|
|
@@ -492,6 +503,8 @@ declare function isChatTextPart(part: ChatMessagePart): part is ChatTextPart;
|
|
|
492
503
|
declare function isChatInteractionPart(part: ChatMessagePart): part is ChatInteractionPart;
|
|
493
504
|
/** Resolve whether a chat message part is a persisted chat plan part */
|
|
494
505
|
declare function isChatPlanPart(part: ChatMessagePart): part is ChatPlanPart;
|
|
506
|
+
/** Resolve whether a chat message part is a persisted work-product anchor */
|
|
507
|
+
declare function isChatWorkProductPart(part: ChatMessagePart): part is ChatWorkProductPart;
|
|
495
508
|
/** Determine if a chat message part represents the completion of a chat step */
|
|
496
509
|
declare function isChatStepFinishPart(part: ChatMessagePart): part is ChatStepFinishPart;
|
|
497
510
|
/** Widened to `unknown` — unlike its siblings this guard also runs over raw
|
|
@@ -594,4 +607,4 @@ declare function historyContentWithAttachments(message: {
|
|
|
594
607
|
parts?: ReadonlyArray<Record<string, unknown>> | null;
|
|
595
608
|
}, header?: string): string;
|
|
596
609
|
|
|
597
|
-
export { type
|
|
610
|
+
export { type ProducerErrorEvent as $, isChatInteractionPart as A, isChatMentionPart as B, type ChatAttachmentKind as C, DEFAULT_ATTACHMENT_PROMPT_HEADER as D, isChatPlanPart as E, isChatStepFinishPart as F, isChatTextPart as G, isChatToolPart as H, isChatWorkProductPart as I, mentionInputToPart as J, mentionPartsFromMessageParts as K, toChatMessageParts as L, type FileMention as M, type ChatTurnRequestPayload as N, type ChatTurnPartInput as O, type ChatTurnFilePartInput as P, type ChatAttachmentInput as Q, ChatTurnInputError as R, type StorableHarnessPartKind as S, type ChatTurnTextPartInput as T, DISPATCH_MAX_MEDIA_PARTS as U, DISPATCH_MAX_PARTS as V, DISPATCH_REQUEST_MAX_BYTES as W, DISPATCH_STRUCTURAL_RESERVE_BYTES as X, type FileMentionsToPartsOptions as Y, INLINE_PARTS_MAX_BYTES as Z, MENTION_MAX_COUNT as _, type ChatAttachmentPart as a, type ProducerNoticeEvent as a0, type ProducerPassthroughEvent as a1, type ProducerPassthroughEventType as a2, type ProducerReasoningEvent as a3, type ProducerTextEvent as a4, type ProducerToolCallEvent as a5, type ProducerToolResultEvent as a6, type ProducerUsageEvent as a7, type ProducerWireEvent as a8, type SandboxMentionPathCheck as a9, assertPromptPartsWithinCap as aa, base64WireLen as ab, buildMentionPromptBlock as ac, chatTurnRequestInit as ad, fileMentionsToParts as ae, formatBytes as af, mediaTypeForMentionPath as ag, mentionKindForPath as ah, parseChatTurnParts as ai, parseFileMentions as aj, promptPartsByteSize as ak, validateSandboxMentionPath as al, type ChatFilePart as b, type ChatImagePart as c, type ChatInteractionPart as d, type ChatMentionKind as e, type ChatMentionPart as f, type ChatMessagePart as g, type ChatNoticePart as h, type ChatPartTime as i, type ChatPlanPart as j, type ChatReasoningPart as k, type ChatStepFinishPart as l, type ChatStepStartPart as m, type ChatSubtaskPart as n, type ChatTextPart as o, type ChatToolPart as p, type ChatToolState as q, type ChatToolStatus as r, type ChatUsageTokens as s, type ChatWorkProductPart as t, attachmentInputToPart as u, attachmentKindForMime as v, attachmentPartsFromMessageParts as w, buildAttachmentPromptBlock as x, historyContentWithAttachments as y, isChatAttachmentPart as z };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { i as WorkProductRecord, j as WorkProductRef, h as WorkProductProvenance } from './types-CEchbvgz.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The review queue is a PROJECTION, not a store — a client-safe pure fold of
|
|
5
|
+
* existing sources into queue items (the `/missions` events.ts pattern: pure
|
|
6
|
+
* data, re-validation at JSON boundaries). The only genuinely-new durable
|
|
7
|
+
* state behind it is the {@link WorkProductRecord} row and its status
|
|
8
|
+
* machine; everything else reads what already exists:
|
|
9
|
+
*
|
|
10
|
+
* - intake: a chat thread for the engagement scope with NO record yet
|
|
11
|
+
* - missing_info: the open record's thread has a PENDING `/interactions` ask
|
|
12
|
+
* - working: record status `draft` (the live token tail stays on the chat
|
|
13
|
+
* surface's existing running-turns endpoint — the projection tracks no
|
|
14
|
+
* live runs, per the reuse-the-primitive invariant)
|
|
15
|
+
* - ready_for_review / changes_requested / approved / blocked: read directly
|
|
16
|
+
* off `WorkProductRecord.status` (blocked surfaces its unresolved count)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
type ReviewQueueState = 'intake' | 'missing_info' | 'working' | 'ready_for_review' | 'changes_requested' | 'approved' | 'blocked';
|
|
20
|
+
/** One row of the review queue projection for an engagement scope */
|
|
21
|
+
interface ReviewQueueItem {
|
|
22
|
+
scopeKey: string;
|
|
23
|
+
state: ReviewQueueState;
|
|
24
|
+
threadId: string | null;
|
|
25
|
+
workProduct?: WorkProductRef & {
|
|
26
|
+
title: string;
|
|
27
|
+
kind: string;
|
|
28
|
+
};
|
|
29
|
+
/** The pending `/interactions` ask parking this scope, when any. */
|
|
30
|
+
pendingAsk?: {
|
|
31
|
+
interactionId: string;
|
|
32
|
+
title: string;
|
|
33
|
+
};
|
|
34
|
+
blockingExceptions: number;
|
|
35
|
+
failedChecks: number;
|
|
36
|
+
provenance?: Pick<WorkProductProvenance, 'profileHash' | 'servingModels'>;
|
|
37
|
+
updatedAt: number;
|
|
38
|
+
}
|
|
39
|
+
/** An engagement-scoped chat thread — the intake candidate source. Products
|
|
40
|
+
* that scope threads already carry a scopeKey-style column. */
|
|
41
|
+
interface ReviewQueueThread {
|
|
42
|
+
scopeKey: string;
|
|
43
|
+
threadId: string;
|
|
44
|
+
updatedAt: number;
|
|
45
|
+
}
|
|
46
|
+
/** A pending `/interactions` ask on a thread (from the existing list
|
|
47
|
+
* endpoint) — the missing_info source. */
|
|
48
|
+
interface ReviewQueuePendingAsk {
|
|
49
|
+
threadId: string;
|
|
50
|
+
interactionId: string;
|
|
51
|
+
title: string;
|
|
52
|
+
}
|
|
53
|
+
/** Existing-source inputs the projection folds — no new stores */
|
|
54
|
+
interface ReviewQueueInputs {
|
|
55
|
+
workProducts: readonly WorkProductRecord[];
|
|
56
|
+
/** Engagement threads with no work product yet → intake items. */
|
|
57
|
+
threads?: readonly ReviewQueueThread[];
|
|
58
|
+
/** Pending asks by thread → missing_info override on open records. */
|
|
59
|
+
pendingAsks?: readonly ReviewQueuePendingAsk[];
|
|
60
|
+
}
|
|
61
|
+
/** Fold the existing sources into queue items, newest first. */
|
|
62
|
+
declare function projectReviewQueue(inputs: ReviewQueueInputs): ReviewQueueItem[];
|
|
63
|
+
/** Re-validate one JSON-boundary row into a queue item; null for junk. The
|
|
64
|
+
* client-side twin of the server projection, for payloads that cross a
|
|
65
|
+
* fetch boundary. */
|
|
66
|
+
declare function parseReviewQueueItem(raw: unknown): ReviewQueueItem | null;
|
|
67
|
+
|
|
68
|
+
export { type ReviewQueueInputs as R, type ReviewQueueItem as a, type ReviewQueuePendingAsk as b, type ReviewQueueState as c, type ReviewQueueThread as d, projectReviewQueue as e, parseReviewQueueItem as p };
|
package/dist/runtime/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
runToolLoop,
|
|
9
9
|
streamToolLoop,
|
|
10
10
|
toLoopEvents
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-7ESQUSAC.js";
|
|
12
12
|
import {
|
|
13
13
|
DEFAULT_TANGLE_BILLING_ENFORCEMENT_ENV_VAR,
|
|
14
14
|
DEFAULT_TANGLE_ROUTER_BASE_URL,
|
|
@@ -24,7 +24,8 @@ import {
|
|
|
24
24
|
tangleExecutionKeyHttpError,
|
|
25
25
|
trimOrNull
|
|
26
26
|
} from "../chunk-JML7WKWU.js";
|
|
27
|
-
import "../chunk-
|
|
27
|
+
import "../chunk-UOAYS72M.js";
|
|
28
|
+
import "../chunk-YEFFHORB.js";
|
|
28
29
|
import {
|
|
29
30
|
__resetCatalogCache,
|
|
30
31
|
buildCatalog,
|
package/dist/sandbox/index.js
CHANGED
|
@@ -60,11 +60,12 @@ import {
|
|
|
60
60
|
writeProfileFilesToBox
|
|
61
61
|
} from "../chunk-3ALFBTIW.js";
|
|
62
62
|
import "../chunk-CQZSAR77.js";
|
|
63
|
-
import "../chunk-
|
|
63
|
+
import "../chunk-WL7XHLDK.js";
|
|
64
64
|
import "../chunk-3EJ6SFJI.js";
|
|
65
65
|
import "../chunk-S5SRJJQG.js";
|
|
66
66
|
import "../chunk-JML7WKWU.js";
|
|
67
|
-
import "../chunk-
|
|
67
|
+
import "../chunk-UOAYS72M.js";
|
|
68
|
+
import "../chunk-YEFFHORB.js";
|
|
68
69
|
export {
|
|
69
70
|
DEFAULT_SANDBOX_RESOURCES,
|
|
70
71
|
ENV_TOTAL_MAX_BYTES,
|
package/dist/teams/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateInviteToken,
|
|
3
|
+
isInviteTokenShape,
|
|
4
|
+
validateInviteToken
|
|
5
|
+
} from "../chunk-DJ4VJIH5.js";
|
|
1
6
|
import {
|
|
2
7
|
INVITATION_EXPIRY_DAYS,
|
|
3
8
|
generateInvitationToken,
|
|
@@ -7,11 +12,6 @@ import {
|
|
|
7
12
|
parseInvitationPermission,
|
|
8
13
|
renderInvitationEmail
|
|
9
14
|
} from "../chunk-2DRYTJHI.js";
|
|
10
|
-
import {
|
|
11
|
-
generateInviteToken,
|
|
12
|
-
isInviteTokenShape,
|
|
13
|
-
validateInviteToken
|
|
14
|
-
} from "../chunk-DJ4VJIH5.js";
|
|
15
15
|
import {
|
|
16
16
|
ASSIGNABLE_WORKSPACE_ROLES,
|
|
17
17
|
ORGANIZATION_ROLES,
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SeatLimitError
|
|
3
|
+
} from "../chunk-MEUNTJL5.js";
|
|
4
|
+
import "../chunk-DJ4VJIH5.js";
|
|
1
5
|
import {
|
|
2
6
|
generateInvitationToken,
|
|
3
7
|
getInvitationExpiresAt,
|
|
@@ -5,10 +9,6 @@ import {
|
|
|
5
9
|
normalizeInvitationEmail,
|
|
6
10
|
parseInvitationPermission
|
|
7
11
|
} from "../chunk-2DRYTJHI.js";
|
|
8
|
-
import {
|
|
9
|
-
SeatLimitError
|
|
10
|
-
} from "../chunk-MEUNTJL5.js";
|
|
11
|
-
import "../chunk-DJ4VJIH5.js";
|
|
12
12
|
import {
|
|
13
13
|
hasWorkspaceRole
|
|
14
14
|
} from "../chunk-6XIAPIW6.js";
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
InviteAcceptPage
|
|
3
|
+
} from "../chunk-VCPZ3HTN.js";
|
|
1
4
|
import {
|
|
2
5
|
MembersPanel
|
|
3
6
|
} from "../chunk-S564OFTL.js";
|
|
4
7
|
import {
|
|
5
8
|
InvitationsPanel
|
|
6
9
|
} from "../chunk-5SXS3YAB.js";
|
|
7
|
-
import {
|
|
8
|
-
InviteAcceptPage
|
|
9
|
-
} from "../chunk-VCPZ3HTN.js";
|
|
10
10
|
import "../chunk-6XIAPIW6.js";
|
|
11
11
|
export {
|
|
12
12
|
InvitationsPanel,
|
package/dist/tools/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
restrictTaxonomy,
|
|
7
7
|
verifyCapabilityToken,
|
|
8
8
|
verifyExpiringCapabilityToken
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-WL7XHLDK.js";
|
|
10
10
|
import {
|
|
11
11
|
DEFAULT_APP_TOOL_PATHS,
|
|
12
12
|
DEFAULT_HEADER_NAMES,
|
|
@@ -19,18 +19,20 @@ import {
|
|
|
19
19
|
readToolArgs
|
|
20
20
|
} from "../chunk-3EJ6SFJI.js";
|
|
21
21
|
import "../chunk-S5SRJJQG.js";
|
|
22
|
+
import {
|
|
23
|
+
createAppToolRuntimeExecutor,
|
|
24
|
+
dispatchAppTool,
|
|
25
|
+
outcomeStatus
|
|
26
|
+
} from "../chunk-UOAYS72M.js";
|
|
22
27
|
import {
|
|
23
28
|
APP_TOOL_NAMES,
|
|
24
29
|
ToolInputError,
|
|
25
30
|
buildAppToolOpenAITools,
|
|
26
|
-
createAppToolRuntimeExecutor,
|
|
27
31
|
customToolToOpenAI,
|
|
28
32
|
defineAppTool,
|
|
29
|
-
dispatchAppTool,
|
|
30
33
|
findCustomTool,
|
|
31
|
-
isAppToolName
|
|
32
|
-
|
|
33
|
-
} from "../chunk-7CTIUCQ4.js";
|
|
34
|
+
isAppToolName
|
|
35
|
+
} from "../chunk-YEFFHORB.js";
|
|
34
36
|
export {
|
|
35
37
|
APP_TOOL_NAMES,
|
|
36
38
|
DEFAULT_APP_TOOL_PATHS,
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { JudgeVerdict } from '@tangle-network/agent-eval';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Trust gate — decides whether an ensemble's scores are allowed to be BELIEVED,
|
|
5
|
+
* one level up from {@link aggregateJudgeVerdicts} (which only reduces ONE
|
|
6
|
+
* artifact's raters to a composite). A composite is a number; this is the check
|
|
7
|
+
* that the number means anything. It is the code "Enforced by" for the
|
|
8
|
+
* measurement-validation skill's after-gate ("is this result allowed to be
|
|
9
|
+
* believed").
|
|
10
|
+
*
|
|
11
|
+
* Three checks, each fail-loud and named in `trustReasons`:
|
|
12
|
+
* (1) inter-rater reliability over the corpus ≥ `irrFloor` — raters that
|
|
13
|
+
* disagree no better than chance carry no signal to optimize against.
|
|
14
|
+
* (2) per-item rater spread ≤ `spreadCeiling` — for EACH item, raters must
|
|
15
|
+
* converge on THAT item.
|
|
16
|
+
* (3) surviving raters per item ≥ `minSurvivors` — a mean over one or two
|
|
17
|
+
* raters is an anecdote, not an ensemble.
|
|
18
|
+
*
|
|
19
|
+
* CRITICAL metric semantics — per-item spread is rater disagreement about the
|
|
20
|
+
* SAME item: `max(score) − min(score)` across the raters that scored THAT item
|
|
21
|
+
* (max over its dimensions), never pooled across different items or across the
|
|
22
|
+
* baseline/candidate sides. Pooling reads a genuine quality gap BETWEEN items as
|
|
23
|
+
* "the raters split" and so trips the gate exactly when the finding is largest —
|
|
24
|
+
* the failure mode the after-gate exists to prevent. The corpus IRR (check 1)
|
|
25
|
+
* leans on the substrate's `interRaterReliability`, whose expected-disagreement
|
|
26
|
+
* denominator already pools across items, so genuine item-to-item variation
|
|
27
|
+
* RAISES reliability rather than lowering it.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** One item's raters: the per-judge verdicts {@link aggregateJudgeVerdicts}
|
|
31
|
+
* reduces, tagged with the item they scored so spread stays within-item. */
|
|
32
|
+
interface TrustItem<D extends string = string> {
|
|
33
|
+
/** Stable item identifier — surfaces in `perItemSpread` and `trustReasons`. */
|
|
34
|
+
itemId: string;
|
|
35
|
+
/** The raters' verdicts for THIS item (one per judge call). A failed judge
|
|
36
|
+
* (`perDimension: null`) is dropped before spread/IRR, never folded as 0. */
|
|
37
|
+
verdicts: readonly JudgeVerdict<D>[];
|
|
38
|
+
}
|
|
39
|
+
/** Thresholds for {@link trustVerdicts}. All overridable; defaults are the
|
|
40
|
+
* conservative after-gate bar. */
|
|
41
|
+
interface TrustThresholds {
|
|
42
|
+
/** Minimum corpus inter-rater reliability (Krippendorff-style α). Below this
|
|
43
|
+
* the raters agree no better than chance. Default 0.2. */
|
|
44
|
+
irrFloor?: number;
|
|
45
|
+
/** Maximum per-item rater spread (`max − min` over a single item's surviving
|
|
46
|
+
* raters, across its dimensions). Above this the raters split ON THAT ITEM.
|
|
47
|
+
* Default 0.5. */
|
|
48
|
+
spreadCeiling?: number;
|
|
49
|
+
/** Minimum surviving (non-failed) raters required per item. Default 3. */
|
|
50
|
+
minSurvivors?: number;
|
|
51
|
+
}
|
|
52
|
+
/** Result of the trust gate. `trustworthy` iff every check passed; `trustReasons`
|
|
53
|
+
* is empty iff `trustworthy`. */
|
|
54
|
+
interface TrustVerdict {
|
|
55
|
+
/** True iff IRR ≥ floor AND every item's spread ≤ ceiling AND every item has
|
|
56
|
+
* ≥ `minSurvivors` surviving raters. */
|
|
57
|
+
trustworthy: boolean;
|
|
58
|
+
/** One entry per FAILED check, each naming its number + the offending value.
|
|
59
|
+
* Empty iff `trustworthy`. */
|
|
60
|
+
trustReasons: string[];
|
|
61
|
+
/** Corpus inter-rater reliability actually measured (the check-1 value). */
|
|
62
|
+
interRaterReliability: number;
|
|
63
|
+
/** Per-item spread (`max − min` over surviving raters, max over dimensions),
|
|
64
|
+
* keyed by `itemId`. The check-2 input, surfaced for drill-down. */
|
|
65
|
+
perItemSpread: Record<string, number>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Decide whether an ensemble's per-item verdicts are trustworthy enough to
|
|
69
|
+
* believe a lift computed from them. Pure: no LLM, no I/O, no clock, no random —
|
|
70
|
+
* the same `items` + `thresholds` always yield the same verdict.
|
|
71
|
+
*
|
|
72
|
+
* Sibling to {@link aggregateJudgeVerdicts}: that reduces ONE item's raters to a
|
|
73
|
+
* composite; this audits the raters ACROSS items and reports whether the
|
|
74
|
+
* composites are believable. Run it on the corpus of held-out items before
|
|
75
|
+
* reporting any lift over their scores.
|
|
76
|
+
*
|
|
77
|
+
* @throws if `items` is empty — an empty corpus has no measurable trust, and a
|
|
78
|
+
* silent `trustworthy: true` over zero evidence is the exact lie the gate
|
|
79
|
+
* exists to refuse.
|
|
80
|
+
*/
|
|
81
|
+
declare function trustVerdicts<D extends string>(items: readonly TrustItem<D>[], thresholds?: TrustThresholds): TrustVerdict;
|
|
82
|
+
|
|
83
|
+
export { type TrustItem as T, type TrustThresholds as a, type TrustVerdict as b, trustVerdicts as t };
|