dsh-task-time-cost 0.2.8-beta.2 → 0.2.9-beta.1

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/lib/index.js CHANGED
@@ -19948,7 +19948,18 @@ function estimateWorkload(input2, route, options = {}) {
19948
19948
  let output2 = { ...template.visibleOutputTokens };
19949
19949
  let tool = { ...template.toolMs };
19950
19950
  const reasons = [...classification.reasons, "serial-conservative-dependencies", "cache-ratio-unknown", "reasoning-budget-unknown"];
19951
- if (history) {
19951
+ const observations = (options.observations ?? []).filter((sample) => Number.isSafeInteger(sample.requestCount) && sample.requestCount >= 1 && Number.isSafeInteger(sample.visibleOutputTokens) && sample.visibleOutputTokens >= 0 && Number.isFinite(sample.toolMs) && sample.toolMs >= 0).slice(-config2.maxSamplesPerBucket);
19952
+ const empirical = observations.length >= config2.minConditionalSamples;
19953
+ if (empirical) {
19954
+ const range = (key) => {
19955
+ const values = observations.map((sample) => sample[key]).sort((a, b) => a - b);
19956
+ return { low: values[Math.floor((values.length - 1) * 0.2)], high: values[Math.ceil((values.length - 1) * 0.8)] };
19957
+ };
19958
+ count3 = range("requestCount");
19959
+ output2 = range("visibleOutputTokens");
19960
+ tool = range("toolMs");
19961
+ reasons.push("local-workload-distribution");
19962
+ } else if (history) {
19952
19963
  for (const key of ["requestCount", "visibleOutputTokens", "toolMs", "samples"]) nonnegative(history[key], key);
19953
19964
  count3 = scaleRange(count3, history.requestCount);
19954
19965
  count3 = { low: Math.max(1, Math.floor(count3.low)), high: Math.max(1, Math.ceil(count3.high)) };
@@ -19979,15 +19990,15 @@ function estimateWorkload(input2, route, options = {}) {
19979
19990
  output2 = { low: Math.max(output2.low, length.low * artifacts), high: Math.max(output2.high, length.high * artifacts * overhead) };
19980
19991
  reasons.push(input2.explicitOutputTokens ? "explicit-output-token-budget" : "requested-length-token-conversion:heuristic");
19981
19992
  }
19982
- const origin = history ? template.sourceKind === "local-measured" ? "local-measured" : "mixed" : template.sourceKind;
19993
+ const origin = empirical ? "local-measured" : history ? template.sourceKind === "local-measured" ? "local-measured" : "mixed" : template.sourceKind;
19983
19994
  return {
19984
19995
  kind: classification.kind,
19985
19996
  low: buildScenario(count3.low, output2.low, tool.low, input2.currentInputTokens, route, classification.kind, template.sourceKind, config2, false, length ? length.low * artifacts : null),
19986
19997
  high: buildScenario(count3.high, output2.high, tool.high, input2.currentInputTokens, route, classification.kind, template.sourceKind, config2, true, length ? length.high * artifacts : null),
19987
19998
  origin,
19988
19999
  reasons,
19989
- workloadSamples: history?.samples ?? 0,
19990
- sourceIds: [template.sourceId ?? `engineering-${template.kind}-v1`, ...history ? [`local-workload:${history.kind}:${history.samples}`] : []]
20000
+ workloadSamples: empirical ? observations.length : history?.samples ?? 0,
20001
+ sourceIds: empirical ? [`local-workload-distribution:${classification.kind}:${observations.length}`] : [template.sourceId ?? `engineering-${template.kind}-v1`, ...history ? [`local-workload:${history.kind}:${history.samples}`] : []]
19991
20002
  };
19992
20003
  }
19993
20004
  function outputLengthFromPrompt(prompt) {
@@ -20232,7 +20243,7 @@ function csvBaselines(text) {
20232
20243
  }
20233
20244
 
20234
20245
  // src/shared/wire.ts
20235
- var ALGORITHM_VERSION = "p0-0.2.4";
20246
+ var ALGORITHM_VERSION = "p0-0.2.9";
20236
20247
  var PACKAGE_NAME = "dsh-task-time-cost";
20237
20248
 
20238
20249
  // src/shared/remote-contract.ts
@@ -22200,10 +22211,14 @@ var EstimatorEngine = class {
22200
22211
  const firstCall = task.calls[0];
22201
22212
  const input2 = this.initialInput.get(key) ?? (firstCall ? this.normalize(firstCall).promptTokens : null);
22202
22213
  const requestMode = task.calls.findLast((call) => call.mode !== "unknown" && (!task.route || call.route.providerId === task.route.providerId && call.route.modelId === task.route.modelId && call.route.reasoningEffort === task.route.reasoningEffort))?.mode ?? "unknown";
22203
- const signature2 = JSON.stringify([task.taskType, task.explicitOutputWords, task.attachmentCount, task.route, requestMode, input2, task.aiPlan]);
22214
+ const route = this.route(task.route, requestMode);
22215
+ const comparable = task.aiPlan ? [] : [...this.repository.tasks.values()].filter((record2) => {
22216
+ const past = record2.current, p = past.performance;
22217
+ return record2.id !== key && record2.learned?.eligible && past.state === "completed" && past.endedAt !== null && past.endedAt <= task.startedAt && record2.savedAt > this.repository.resetAt && task.startedAt - past.endedAt <= this.config.historyRetentionDays * 864e5 && past.taskType === task.taskType && p?.providerId === route.providerId && p.modelId === route.modelId && p.endpointFingerprint === route.endpointFingerprint && p.reasoningEffort === route.reasoningEffort && p.serviceTier === route.serviceTier;
22218
+ }).sort((a, b) => a.current.endedAt - b.current.endedAt).slice(-this.config.maxSamplesPerBucket);
22219
+ const signature2 = JSON.stringify([task.taskType, task.explicitOutputWords, task.attachmentCount, task.route, requestMode, input2, task.aiPlan, comparable.map((record2) => record2.id)]);
22204
22220
  const previous = this.plans.get(key);
22205
22221
  if (previous?.signature === signature2) return structuredClone(previous.plan);
22206
- const route = this.route(task.route, requestMode);
22207
22222
  const templates = this.templates(route);
22208
22223
  let plan = estimateWorkload(
22209
22224
  {
@@ -22213,7 +22228,13 @@ var EstimatorEngine = class {
22213
22228
  ...task.explicitOutputWords ? { explicitOutputTokens: { low: task.explicitOutputWords, high: task.explicitOutputWords * 2 } } : {}
22214
22229
  },
22215
22230
  route,
22216
- { config: this.engineConfig(), templates, history: [...this.workloadHistory.values()], rules: [{ id: "durable-classification", kind: task.taskType, patterns: [".*"] }] }
22231
+ {
22232
+ config: this.engineConfig(),
22233
+ templates,
22234
+ history: [...this.workloadHistory.values()],
22235
+ observations: comparable.map((record2) => ({ requestCount: record2.learned.calls, visibleOutputTokens: record2.learned.visibleOutputTokens, toolMs: record2.learned.toolWorkMs })),
22236
+ rules: [{ id: "durable-classification", kind: task.taskType, patterns: [".*"] }]
22237
+ }
22217
22238
  );
22218
22239
  if (task.aiPlan) plan = applyAiPlan(plan, task.aiPlan, route, this.currentInput.get(key) ?? input2, this.engineConfig());
22219
22240
  this.plans.set(key, { signature: signature2, plan });
@@ -22369,7 +22390,10 @@ var EstimatorEngine = class {
22369
22390
  const low = sumKnown(lows), high = sumKnown(highs);
22370
22391
  return low === null || high === null ? null : { low, high };
22371
22392
  });
22372
- const outputScenarios = [plan.low, plan.high].map((scenario) => scenario.requests.filter((request) => !completedIds.includes(request.id)).reduce((sum, request) => ({ low: sum.low + request.visibleOutputTokens.low, high: sum.high + request.visibleOutputTokens.high }), { low: 0, high: 0 }));
22393
+ const outputScenarios = [plan.low, plan.high].map((scenario) => scenario.requests.filter((request) => !completedIds.includes(request.id)).reduce((sum, request) => {
22394
+ const generated = progress[request.id]?.generatedTextTokens ?? 0;
22395
+ return { low: sum.low + Math.max(0, request.visibleOutputTokens.low - generated), high: sum.high + Math.max(0, request.visibleOutputTokens.high - generated) };
22396
+ }, { low: 0, high: 0 }));
22373
22397
  const estimatedRemainingOutputTokens = task.endedAt !== null ? { low: 0, high: 0 } : errors || planExhausted ? null : { low: Math.min(...outputScenarios.map((value) => value.low)), high: Math.max(...outputScenarios.map((value) => value.high)) };
22374
22398
  const estimatedTokens = tokenScenarios.every((span3) => span3 !== null) ? { low: Math.max(totalTokens ?? 0, Math.min(...tokenScenarios.map((span3) => span3.low))), high: Math.max(totalTokens ?? 0, ...tokenScenarios.map((span3) => span3.high)) } : null;
22375
22399
  const initial = saved?.current.initial ?? (task.endedAt === null && task.promptChars > 0 && this.startedHere.has(key) ? { estimatedTotalMs: remaining ? { low: elapsedActive + remaining.low, high: elapsedActive + remaining.high } : null, estimatedTotalCost: totalCost } : null);
@@ -22910,7 +22934,7 @@ async function apply(ctx, rawConfig) {
22910
22934
  name: "task_estimate_plan",
22911
22935
  description: "Update the current task ETA from your assessment of the work still needed. Submit after understanding a multi-step task, and revise when its scope or remaining work materially changes. The Host combines the plan with this route\u2019s observed response times. These are reference estimates, not completion promises.",
22912
22936
  parameters: { type: "object", properties: {
22913
- remainingCalls: { type: "object", description: "Future model requests after this reporting call, including final answer and verification; ordered integer bounds, 1\u2013500.", properties: { low: { type: "integer", minimum: 1, maximum: 500 }, high: { type: "integer", minimum: 1, maximum: 500 } }, required: ["low", "high"], additionalProperties: false },
22937
+ remainingCalls: { type: "object", description: "Future model response rounds after this reporting call, including final answer and verification; 1\u2013500. Count one response containing many tool calls as ONE model request, not one per tool or file. Reuse the observed work pattern to bound remaining rounds.", properties: { low: { type: "integer", minimum: 1, maximum: 500 }, high: { type: "integer", minimum: 1, maximum: 500 } }, required: ["low", "high"], additionalProperties: false },
22914
22938
  remainingOutputTokens: { type: "object", description: "Total future visible text/tool-argument tokens, excluding hidden reasoning; ordered bounds, 0\u20132000000.", properties: { low: { type: "integer", minimum: 0, maximum: 2e6 }, high: { type: "integer", minimum: 0, maximum: 2e6 } }, required: ["low", "high"], additionalProperties: false },
22915
22939
  remainingToolSeconds: { type: "object", description: "Future tool execution time, excluding model requests and human waits; ordered bounds, 0\u201386400 seconds.", properties: { low: { type: "integer", minimum: 0, maximum: 86400 }, high: { type: "integer", minimum: 0, maximum: 86400 } }, required: ["low", "high"], additionalProperties: false },
22916
22940
  stage: { type: "string", enum: ["planning", "implementation", "verification", "finishing"] }
@@ -22926,7 +22950,7 @@ async function apply(ctx, rawConfig) {
22926
22950
  ctx.systemPrompt.section({
22927
22951
  name: "task-estimate-planning",
22928
22952
  order: 1e3,
22929
- text: "For a multi-step user task, submit a task_estimate_plan after understanding the scope, preferably alongside your initial work tools. Include implementation, verification and delivery. Revise it only when scope or remaining work materially changes or the budget is exceeded. Count work remaining after the reporting call. Use task_estimator snapshot route timings when useful; never invent measured performance or treat call count as percentage complete. Skip planning for trivial answers. This uses the executing agent; do not start another model request just to estimate or poll."
22953
+ text: "For a multi-step user task, submit a task_estimate_plan after understanding the scope, preferably alongside your initial work tools. Include implementation, verification and delivery. Revise it only when scope or remaining work materially changes or the budget is exceeded. Count work remaining after the reporting call. Count model response rounds, not individual tools, files, commands or parallel calls; one model response can contain many tools. Estimate tool wall time on the critical path, not the sum of parallel execution times. Prefer concrete bounds for the known scope and revise when evidence changes; do not add speculative optional work. Use task_estimator snapshot route timings when useful; never invent measured performance or treat call count as percentage complete. Skip planning for trivial answers. This uses the executing agent; do not start another model request just to estimate or poll."
22930
22954
  });
22931
22955
  for (const agent of ctx.agents.list()) safe(agent, () => {
22932
22956
  projection.stateOf(agent.session);