@pasko70/pibo 1.15.0 → 1.16.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.
Files changed (37) hide show
  1. package/context/pibo-native-tooling.md +1 -1
  2. package/dist/apps/chat/model-catalog.js +2 -0
  3. package/dist/apps/chat/web-app.js +11 -0
  4. package/dist/apps/chat-ui/assets/{dist-Bi74WdBU.js → dist-BISHtc7t.js} +1 -1
  5. package/dist/apps/chat-ui/assets/{dist-Dq3i1LVZ.js → dist-CGlkj4C3.js} +1 -1
  6. package/dist/apps/chat-ui/assets/{dist-Dy5L1xOD.js → dist-CNZT1Z0L.js} +1 -1
  7. package/dist/apps/chat-ui/assets/{dist-BFwSuVfD.js → dist-CsgWBaSD.js} +1 -1
  8. package/dist/apps/chat-ui/assets/{dist-BYTi90un.js → dist-DDJJ8GK-.js} +1 -1
  9. package/dist/apps/chat-ui/assets/{dist-DS1ScAMf.js → dist-DXkRuRed.js} +1 -1
  10. package/dist/apps/chat-ui/assets/{dist-CuIwfDgF.js → dist-DltoBBze.js} +1 -1
  11. package/dist/apps/chat-ui/assets/{dist-DUgrMhHX.js → dist-DoMBxyQP.js} +1 -1
  12. package/dist/apps/chat-ui/assets/{dist-Zy3KnDp7.js → dist-I7cqk1W9.js} +1 -1
  13. package/dist/apps/chat-ui/assets/{dist-CYMM1_bS.js → dist-gWAQGdkD.js} +1 -1
  14. package/dist/apps/chat-ui/assets/index-CiKRTg5a.js +237 -0
  15. package/dist/apps/chat-ui/assets/index-E0RmuCkY.css +1 -0
  16. package/dist/apps/chat-ui/index.html +2 -2
  17. package/dist/apps/chat-vscode-web/assets/index-JHIrxu8c.js +41 -0
  18. package/dist/apps/chat-vscode-web/index.html +1 -1
  19. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  20. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.16.1.vsix +0 -0
  21. package/dist/core/routed-session.js +17 -0
  22. package/dist/core/runtime.js +2 -0
  23. package/dist/core/session-router.js +20 -6
  24. package/dist/debug/web-streaming-benchmark-analysis.js +23 -0
  25. package/dist/debug/web-streaming-browser-library.js +0 -1
  26. package/dist/debug/web.js +4 -2
  27. package/dist/gateway/server.js +1 -0
  28. package/dist/plugins/builtin.js +4 -9
  29. package/dist/providers/openai-compat.js +6 -1
  30. package/dist/providers/qwen-token-plan.js +67 -0
  31. package/dist/shared/trace-event-projection.js +15 -15
  32. package/dist/shared/trace-live-reducer.js +1 -0
  33. package/package.json +1 -1
  34. package/dist/apps/chat-ui/assets/index-BZO4zpMi.js +0 -237
  35. package/dist/apps/chat-ui/assets/index-CoCl8FG8.css +0 -1
  36. package/dist/apps/chat-vscode-web/assets/index-V_pKPVAm.js +0 -41
  37. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.15.0.vsix +0 -0
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <meta name="theme-color" content="#101d22" />
7
7
  <title>Pibo</title>
8
- <script type="module" crossorigin src="/apps/chat-vscode/assets/index-V_pKPVAm.js"></script>
8
+ <script type="module" crossorigin src="/apps/chat-vscode/assets/index-JHIrxu8c.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/apps/chat-vscode/assets/index-Dupu1DzQ.css">
10
10
  </head>
11
11
  <body>
@@ -818,6 +818,22 @@ export class RoutedSession {
818
818
  } : {}),
819
819
  };
820
820
  }
821
+ async getStatusSnapshot() {
822
+ const contextUsage = this.getContextUsage();
823
+ const providerUsage = await this.getProviderUsage();
824
+ return {
825
+ ...this.getStatus(),
826
+ activeModel: this.getActiveModel(),
827
+ contextUsage: contextUsage
828
+ ? {
829
+ tokens: contextUsage.tokens ?? undefined,
830
+ contextWindow: contextUsage.contextWindow ?? undefined,
831
+ percent: contextUsage.percent ?? undefined,
832
+ }
833
+ : contextUsage,
834
+ ...(providerUsage ? { providerUsage } : {}),
835
+ };
836
+ }
821
837
  getContextUsage() {
822
838
  return this.runtime.session.getContextUsage();
823
839
  }
@@ -1261,6 +1277,7 @@ export class RoutedSession {
1261
1277
  return await gatewayAction.execute({
1262
1278
  piboSessionId: this.piboSessionId,
1263
1279
  getStatus: () => this.getStatus(),
1280
+ getStatusSnapshot: () => this.getStatusSnapshot(),
1264
1281
  getContextUsage: () => this.getContextUsage(),
1265
1282
  getActiveModel: () => this.getActiveModel(),
1266
1283
  getProviderUsage: () => this.getProviderUsage(),
@@ -22,6 +22,7 @@ import { DEFAULT_USER_TIMEZONE } from "./user-settings.js";
22
22
  import { registerMiniMaxProvider } from "../providers/minimax.js";
23
23
  import { registerGlmProvider } from "../providers/glm.js";
24
24
  import { registerOpenAiGpt56Models } from "../providers/openai-gpt56.js";
25
+ import { registerQwenTokenPlanProvider } from "../providers/qwen-token-plan.js";
25
26
  import { PIBO_APP_CONTEXT } from "../app-context.js";
26
27
  import { createRuntimeToolDefinition } from "../tools/runtime/tool.js";
27
28
  import { RuntimeSessionRegistry } from "../tools/runtime/registry.js";
@@ -291,6 +292,7 @@ export async function createPiboRuntime(options = {}) {
291
292
  registerOpenAiGpt56Models(services.modelRegistry);
292
293
  registerMiniMaxProvider(services.modelRegistry);
293
294
  registerGlmProvider(services.modelRegistry);
295
+ registerQwenTokenPlanProvider(services.modelRegistry);
294
296
  const ownsLocalRuntimeRegistry = options.runtimeToolController === undefined && profile.tools.some(isEnabledRuntimeTool);
295
297
  const localRuntimeRegistry = ownsLocalRuntimeRegistry ? new RuntimeSessionRegistry({ cwd: runtimeCwd }) : undefined;
296
298
  const runtimeToolController = options.runtimeToolController
@@ -23,6 +23,7 @@ import { PiboRuntimeTelemetryRecorder } from "./runtime-telemetry.js";
23
23
  import { createPiboProviderTelemetryExtension } from "./provider-telemetry.js";
24
24
  import { AsyncTelemetryWriter } from "../data/telemetry-writer.js";
25
25
  const DEFAULT_SUBAGENT_REPLY_TIMEOUT_MS = 10 * 60 * 1000;
26
+ const DEFAULT_SUBAGENT_MAX_DEPTH = 3;
26
27
  const DEFAULT_ROUTED_SESSION_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
27
28
  const DEFAULT_ROUTED_SESSION_DISPOSE_TIMEOUT_MS = 30 * 1000;
28
29
  export const LOOP_RUNTIME_RETRY_DEFAULTS = {
@@ -43,7 +44,10 @@ export function resolvePiboSessionInitialFastMode(session) {
43
44
  const value = session.metadata?.initialFastMode;
44
45
  return typeof value === "boolean" ? value : undefined;
45
46
  }
46
- function profileForSession(baseProfile, piSessionId, parentPiSessionId) {
47
+ function hasReachedSubagentMaxDepth(subagent, depth) {
48
+ return depth >= (subagent.maxDepth ?? DEFAULT_SUBAGENT_MAX_DEPTH);
49
+ }
50
+ function profileForSession(baseProfile, piSessionId, parentPiSessionId, subagentDepth) {
47
51
  const options = {
48
52
  profileName: baseProfile.profileName,
49
53
  sessionId: piSessionId,
@@ -59,7 +63,7 @@ function profileForSession(baseProfile, piSessionId, parentPiSessionId) {
59
63
  subagentFast: baseProfile.subagentFast,
60
64
  skills: baseProfile.skills,
61
65
  tools: baseProfile.tools,
62
- subagents: baseProfile.subagents,
66
+ subagents: baseProfile.subagents.filter((subagent) => !hasReachedSubagentMaxDepth(subagent, subagentDepth)),
63
67
  mcpServers: baseProfile.mcpServers,
64
68
  contextFiles: baseProfile.contextFiles,
65
69
  piPackages: baseProfile.piPackages,
@@ -479,6 +483,15 @@ export class PiboSessionRouter {
479
483
  getSessionRuntimeStatus(piboSessionId) {
480
484
  return this.sessions.get(piboSessionId)?.getStatus();
481
485
  }
486
+ async getSessionStatusSnapshot(piboSessionId) {
487
+ const session = await this.getOrCreateSession(piboSessionId);
488
+ try {
489
+ return await session.getStatusSnapshot();
490
+ }
491
+ finally {
492
+ this.scheduleIdleSessionEvictionIfIdle(piboSessionId);
493
+ }
494
+ }
482
495
  async setLiveSessionActiveModel(piboSessionId, model) {
483
496
  const session = this.sessions.get(piboSessionId);
484
497
  if (!session)
@@ -700,6 +713,7 @@ export class PiboSessionRouter {
700
713
  const modelDefaults = this.resolveModelDefaults();
701
714
  const activeModel = this.ensureSessionActiveModel(piboSession, profile, parentPiSessionId, modelDefaults);
702
715
  const initialThinkingLevel = resolvePiboSessionInitialThinkingLevel(piboSession);
716
+ const sessionProfile = profileForSession(profile, piboSession.piSessionId, parentPiSessionId, this.getSubagentDepth(piboSession.id));
703
717
  const userSettings = loadPiboUserSettings();
704
718
  const telemetryExtension = this.telemetryStore
705
719
  ? createPiboProviderTelemetryExtension({ store: this.telemetryStore, writer: this.telemetryWriter, session: piboSession, model: activeModel })
@@ -709,7 +723,7 @@ export class PiboSessionRouter {
709
723
  persistSession: this.options.persistSession,
710
724
  thinkingLevel: initialThinkingLevel ?? this.options.thinkingLevel,
711
725
  retryDefaults: resolvePiboSessionRetryDefaults(piboSession.kind, this.options.retryDefaults),
712
- profile: profileForSession(profile, piboSession.piSessionId, parentPiSessionId),
726
+ profile: sessionProfile,
713
727
  extensionFactories: [
714
728
  ...(telemetryExtension ? [telemetryExtension] : []),
715
729
  ...(this.options.extensionFactories ?? []),
@@ -726,7 +740,7 @@ export class PiboSessionRouter {
726
740
  getActiveMessage: () => session?.getActiveMessage(),
727
741
  },
728
742
  });
729
- const initialFastMode = resolvePiboSessionInitialFastMode(piboSession) ?? selectRequestedFastMode(profileForSession(profile, piboSession.piSessionId, parentPiSessionId), modelDefaults) ?? false;
743
+ const initialFastMode = resolvePiboSessionInitialFastMode(piboSession) ?? selectRequestedFastMode(sessionProfile, modelDefaults) ?? false;
730
744
  session = new RoutedSession(piboSession.id, runtime, this.emitOutput, this.pluginRegistry, this.options.forwardPiEvents ?? false, this.telemetryRecorder
731
745
  ? (id, event, context) => this.telemetryRecorder?.recordPiEvent(id, event, { session: this.sessionStore.get(id), status: context.status, activeEventId: context.activeEventId })
732
746
  : undefined, initialFastMode, (result, event) => this.handleSessionOperation(result, event), (id, opts) => this.killChildSessions(id, opts), (state) => {
@@ -952,8 +966,8 @@ export class PiboSessionRouter {
952
966
  };
953
967
  }
954
968
  assertSubagentDepth(parentPiboSessionId, subagent) {
955
- const maxDepth = subagent.maxDepth ?? 3;
956
- if (this.getSubagentDepth(parentPiboSessionId) >= maxDepth) {
969
+ const maxDepth = subagent.maxDepth ?? DEFAULT_SUBAGENT_MAX_DEPTH;
970
+ if (hasReachedSubagentMaxDepth(subagent, this.getSubagentDepth(parentPiboSessionId))) {
957
971
  throw new Error(`Subagent "${subagent.name}" exceeded max depth ${maxDepth} from Pibo session "${parentPiboSessionId}"`);
958
972
  }
959
973
  }
@@ -12,6 +12,29 @@ const URL_COMPARISON_MAX_FIRST_LIVE_LATENCY_DELTA_MS = 250;
12
12
  const URL_COMPARISON_MAX_FIRST_VISIBLE_LATENCY_DELTA_MS = 300;
13
13
  const PROVIDER_MIN_TEXT_PRESERVATION_RATIO = 0.95;
14
14
  const PROVIDER_MIN_REASONING_PRESERVATION_RATIO = 0.95;
15
+ const X_ACCEL_BUFFERING_REGRESSION = "SSE X-Accel-Buffering header is not no";
16
+ export function evaluateStreamingSseHeaderRegressions(benchmark, url) {
17
+ if (!benchmark.sse?.requested)
18
+ return [];
19
+ const visibleValue = benchmark.sse.headers?.["x-accel-buffering"]?.trim().toLowerCase();
20
+ if (visibleValue)
21
+ return visibleValue === "no" ? [] : [X_ACCEL_BUFFERING_REGRESSION];
22
+ return isDirectStreamingBenchmarkUrl(url) ? [X_ACCEL_BUFFERING_REGRESSION] : [];
23
+ }
24
+ function isDirectStreamingBenchmarkUrl(value) {
25
+ try {
26
+ const hostname = new URL(value).hostname.toLowerCase();
27
+ return hostname === "localhost"
28
+ || hostname.endsWith(".localhost")
29
+ || hostname === "0.0.0.0"
30
+ || hostname === "::1"
31
+ || hostname === "[::1]"
32
+ || /^127(?:\.\d{1,3}){3}$/.test(hostname);
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ }
15
38
  export function scoreStreamingBenchmark(benchmark) {
16
39
  const debugDelta = benchmark.debug.delta ?? {};
17
40
  const textDeltaCount = numberField(debugDelta, "textDeltaCount");
@@ -645,7 +645,6 @@ function streamingBenchmarkRegressions(result) {
645
645
  if (result.sse && result.sse.requested) {
646
646
  if (!result.sse.installed) failures.push('SSE fetch probe unavailable');
647
647
  if (result.sse.status && result.sse.status !== 200) failures.push('SSE fetch status ' + result.sse.status);
648
- if (result.sse.headers && String(result.sse.headers['x-accel-buffering'] || '').toLowerCase() !== 'no') failures.push('SSE X-Accel-Buffering header is not no');
649
648
  if (result.sse.errors && result.sse.errors.length) failures.push('SSE fetch errors: ' + result.sse.errors.slice(0, 2).join('; '));
650
649
  if (!traceCatchupRequested && expectedDeltas !== undefined && result.sse.textEventCount < expectedDeltas) failures.push('SSE text events ' + result.sse.textEventCount + ' < fixture deltas ' + expectedDeltas);
651
650
  if (!traceCatchupRequested && expectedReasoningDeltas !== undefined && result.sse.reasoningEventCount < expectedReasoningDeltas) failures.push('SSE reasoning events ' + result.sse.reasoningEventCount + ' < fixture reasoning deltas ' + expectedReasoningDeltas);
package/dist/debug/web.js CHANGED
@@ -3,7 +3,7 @@ import { listBrowserUseCdpTargets, selectBestChatTarget, formatBrowserUseTargets
3
3
  import { CdpClient } from "../tools/cdp-client.js";
4
4
  import { diffSnapshots, formatSnapshot, formatSnapshotDiff, formatWatch } from "./web-render-analysis.js";
5
5
  import { formatStreamingBenchmarkAssertionError, formatStreamingBenchmarkCompactReport, formatStreamingBenchmarkResult, streamingBenchmarkReportRows, } from "./web-streaming-report.js";
6
- import { applyExpectedStreamingRegressions, attachStreamingProviderTelemetryToBenchmarks, evaluateStreamingLivePipelineRegressions, evaluateStreamingProviderRegressions, readStreamingBenchmarkArtifact, readStreamingBenchmarkRuns, scoreStreamingBenchmark, streamingBenchmarkReportTarget, summarizeStreamingBenchmarkGroup, summarizeStreamingBenchmarkUrlComparison, summarizeStreamingCadence, summarizeStreamingLivePipeline, summarizeStreamingProviderPreservation, } from "./web-streaming-benchmark-analysis.js";
6
+ import { applyExpectedStreamingRegressions, attachStreamingProviderTelemetryToBenchmarks, evaluateStreamingLivePipelineRegressions, evaluateStreamingProviderRegressions, evaluateStreamingSseHeaderRegressions, readStreamingBenchmarkArtifact, readStreamingBenchmarkRuns, scoreStreamingBenchmark, streamingBenchmarkReportTarget, summarizeStreamingBenchmarkGroup, summarizeStreamingBenchmarkUrlComparison, summarizeStreamingCadence, summarizeStreamingLivePipeline, summarizeStreamingProviderPreservation, } from "./web-streaming-benchmark-analysis.js";
7
7
  import { collectStreamingProviderTelemetry, collectStreamingProviderTelemetryFromSelectedBrowserSession, collectStreamingProviderTelemetryFromSession, collectStreamingProviderTelemetryFromTurn, } from "./web-streaming-provider-telemetry.js";
8
8
  import { buildStreamingBenchmarkExpression, streamingBenchmarkEventSourceProbeScript, streamingBenchmarkFixtureHtml } from "./web-streaming-browser-scripts.js";
9
9
  import { buildSnapshotExpression, buildWatchExpression } from "./web-snapshot-browser-scripts.js";
@@ -12,7 +12,7 @@ import { compactTarget, limitStdout, readBaselineSnapshot, writeArtifact, writeL
12
12
  import { applyNegativeStreamingProfile, DEFAULT_DEPTH_LIMIT, DEFAULT_EVENT_LIMIT, DEFAULT_NODE_LIMIT, DEFAULT_TEXT_LIMIT, parseDuration, parseFixtureMix, parseFixturePreludeMessages, parseFixtureProfile, parseNegativeProfile, parseOptions, parseRuns, presetScope, resolveScope, resolveStreamingBenchmarkCompareUrl, resolveStreamingBenchmarkHostedCompareUrl } from "./web-options.js";
13
13
  export { formatStreamingBenchmarkAssertionSummary, formatStreamingBenchmarkUrlComparison, summarizeStreamingSelectedLiveEventSource } from "./web-streaming-report.js";
14
14
  export { resolveStreamingBenchmarkHostedCompareUrlFromValues } from "./web-options.js";
15
- export { attachStreamingProviderTelemetryToBenchmark, evaluateStreamingBenchmarkAssertion, evaluateStreamingBenchmarkUrlComparisonRegressions, evaluateStreamingLivePipelineRegressions, evaluateStreamingProviderRegressions, summarizeStreamingBenchmarkUrlComparison, summarizeStreamingBenchmarks, summarizeStreamingLivePipeline, summarizeStreamingProviderPreservation } from "./web-streaming-benchmark-analysis.js";
15
+ export { attachStreamingProviderTelemetryToBenchmark, evaluateStreamingBenchmarkAssertion, evaluateStreamingBenchmarkUrlComparisonRegressions, evaluateStreamingLivePipelineRegressions, evaluateStreamingProviderRegressions, evaluateStreamingSseHeaderRegressions, summarizeStreamingBenchmarkUrlComparison, summarizeStreamingBenchmarks, summarizeStreamingLivePipeline, summarizeStreamingProviderPreservation } from "./web-streaming-benchmark-analysis.js";
16
16
  export { collectStreamingProviderTelemetryFromSelectedBrowserSession, collectStreamingProviderTelemetryFromSession, collectStreamingProviderTelemetryFromTurn, summarizeStreamingProviderTelemetry } from "./web-streaming-provider-telemetry.js";
17
17
  export { formatWatch, inferWatchFlickers } from "./web-render-analysis.js";
18
18
  export { analyzeStreamingRenderOrderCapture } from "./web-render-order-analysis.js";
@@ -485,6 +485,7 @@ async function runStreamingBenchmarkSeries(client, runs, durationMs, options) {
485
485
  }
486
486
  async function runStreamingBenchmark(client, durationMs, options = {}) {
487
487
  await client.send("Page.bringToFront").catch(() => undefined);
488
+ const targetUrl = await currentBrowserUrl(client);
488
489
  const benchmarkTimeoutMs = durationMs + (options.startBackendFixture ? 20_000 : 10_000);
489
490
  const benchmark = await client.evaluateJson(buildStreamingBenchmarkExpression(durationMs, options), benchmarkTimeoutMs);
490
491
  const withRenderOrder = { ...benchmark, renderOrder: analyzeStreamingRenderOrderCapture(benchmark.renderOrder) };
@@ -496,6 +497,7 @@ async function runStreamingBenchmark(client, durationMs, options = {}) {
496
497
  ...withCadence,
497
498
  regressions: [
498
499
  ...withCadence.regressions,
500
+ ...evaluateStreamingSseHeaderRegressions(withCadence, targetUrl),
499
501
  ...(withCadence.renderOrder?.analysis?.regressions ?? []),
500
502
  ...evaluateStreamingLivePipelineRegressions(withCadence),
501
503
  ...evaluateStreamingProviderRegressions(withCadence),
@@ -330,6 +330,7 @@ export class PiboGatewayServer {
330
330
  findSessions: (input) => this.requireSessionStore().find(input),
331
331
  listSessions: () => this.requireSessionStore().list?.() ?? [],
332
332
  getSessionRuntimeStatus: (piboSessionId) => this.requireRouter().getSessionRuntimeStatus(piboSessionId),
333
+ getSessionStatusSnapshot: (piboSessionId) => this.requireRouter().getSessionStatusSnapshot(piboSessionId),
333
334
  listSessionRuntimeStatuses: () => this.requireRouter().listSessionRuntimeStatuses(),
334
335
  listRuns: (options) => this.requireRouter().listRuns(options),
335
336
  snapshotSignalSession: (piboSessionId) => this.requireRouter().snapshotSignalSession(piboSessionId),
@@ -22,6 +22,7 @@ const LOGIN_PROVIDERS = [
22
22
  { id: "openai-codex", name: "OpenAI (ChatGPT Plus/Pro)", authMethods: ["device_code"] },
23
23
  { id: "openai", name: "OpenAI API", authMethods: ["api_key"] },
24
24
  { id: "anthropic", name: "Anthropic", authMethods: ["api_key"] },
25
+ { id: "qwen-token-plan", name: "Qwen Token Plan", authMethods: ["api_key"] },
25
26
  { id: "kimi-coding", name: "Kimi for Coding", authMethods: ["api_key"] },
26
27
  { id: "google", name: "Google Gemini", authMethods: ["api_key"] },
27
28
  { id: "groq", name: "Groq", authMethods: ["api_key"] },
@@ -188,14 +189,8 @@ export const piboCorePlugin = definePiboPlugin({
188
189
  name: "status",
189
190
  description: "Return current session status with context usage quota.",
190
191
  slashCommands: ["status"],
191
- async execute(context) {
192
- const providerUsage = await context.getProviderUsage();
193
- return {
194
- ...context.getStatus(),
195
- activeModel: context.getActiveModel(),
196
- contextUsage: context.getContextUsage(),
197
- ...(providerUsage ? { providerUsage } : {}),
198
- };
192
+ execute(context) {
193
+ return context.getStatusSnapshot();
199
194
  },
200
195
  });
201
196
  api.registerGatewayAction({
@@ -368,7 +363,7 @@ export const piboCorePlugin = definePiboPlugin({
368
363
  api.registerGatewayAction({
369
364
  name: "model",
370
365
  description: "Open the interactive model selector for authenticated providers.",
371
- slashCommands: ["model", "models"],
366
+ slashCommands: ["model"],
372
367
  async execute() {
373
368
  const catalog = await loadModelCatalog(process.cwd());
374
369
  return {
@@ -8,6 +8,8 @@ export function buildOpenAiCompatConfig(spec, baseModels = []) {
8
8
  api: OPENAI_COMPLETIONS_API,
9
9
  baseUrl: spec.baseUrl,
10
10
  reasoning: Boolean(model.reasoning),
11
+ thinkingLevelMap: model.thinkingLevelMap ? { ...model.thinkingLevelMap } : undefined,
12
+ compat: model.compat ? { ...model.compat } : undefined,
11
13
  input: [...model.input],
12
14
  cost: { ...model.cost },
13
15
  contextWindow: model.contextWindow,
@@ -19,6 +21,8 @@ export function buildOpenAiCompatConfig(spec, baseModels = []) {
19
21
  api: OPENAI_COMPLETIONS_API,
20
22
  baseUrl: spec.baseUrl,
21
23
  reasoning: model.reasoning ?? false,
24
+ thinkingLevelMap: model.thinkingLevelMap ? { ...model.thinkingLevelMap } : undefined,
25
+ compat: model.compat ? { ...model.compat } : undefined,
22
26
  input: [...model.input],
23
27
  cost: { ...model.cost },
24
28
  contextWindow: model.contextWindow,
@@ -30,9 +34,10 @@ export function buildOpenAiCompatConfig(spec, baseModels = []) {
30
34
  for (const entry of customEntries)
31
35
  byId.set(entry.id, entry);
32
36
  return {
37
+ name: spec.name,
33
38
  baseUrl: spec.baseUrl,
34
39
  api: OPENAI_COMPLETIONS_API,
35
- apiKey: spec.apiKeyEnv,
40
+ apiKey: `$${spec.apiKeyEnv}`,
36
41
  models: [...byId.values()],
37
42
  };
38
43
  }
@@ -0,0 +1,67 @@
1
+ import { OPENAI_COMPLETIONS_API, registerOpenAiCompatProvider, resetOpenAiCompatProviderRegistration, unregisterOpenAiCompatProvider, } from "./openai-compat.js";
2
+ export const QWEN_TOKEN_PLAN_PROVIDER_ID = "qwen-token-plan";
3
+ export const QWEN_TOKEN_PLAN_PROVIDER_NAME = "Qwen Token Plan";
4
+ export const QWEN_TOKEN_PLAN_DEFAULT_BASE_URL = "https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1";
5
+ export const QWEN_TOKEN_PLAN_API_KEY_ENV = "QWEN_TOKEN_PLAN_API_KEY";
6
+ const QWEN_COMPAT = {
7
+ supportsStore: false,
8
+ supportsDeveloperRole: false,
9
+ thinkingFormat: "qwen",
10
+ maxTokensField: "max_tokens",
11
+ };
12
+ function qwenModel(id, name, input) {
13
+ return {
14
+ id,
15
+ name,
16
+ reasoning: true,
17
+ compat: QWEN_COMPAT,
18
+ contextWindow: 1_000_000,
19
+ maxTokens: 65_536,
20
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
21
+ input,
22
+ };
23
+ }
24
+ export const QWEN_TOKEN_PLAN_MODELS = [
25
+ qwenModel("qwen3.7-max", "Qwen3.7 Max", ["text"]),
26
+ qwenModel("qwen3.7-plus", "Qwen3.7 Plus", ["text", "image"]),
27
+ qwenModel("qwen3.6-plus", "Qwen3.6 Plus", ["text", "image"]),
28
+ qwenModel("qwen3.6-flash", "Qwen3.6 Flash", ["text", "image"]),
29
+ ];
30
+ function resolveBaseUrl() {
31
+ const envOverride = process.env.PIBO_QWEN_TOKEN_PLAN_BASE_URL;
32
+ return envOverride && envOverride.length > 0
33
+ ? envOverride
34
+ : QWEN_TOKEN_PLAN_DEFAULT_BASE_URL;
35
+ }
36
+ function buildQwenTokenPlanSpec() {
37
+ return {
38
+ id: QWEN_TOKEN_PLAN_PROVIDER_ID,
39
+ name: QWEN_TOKEN_PLAN_PROVIDER_NAME,
40
+ baseUrl: resolveBaseUrl(),
41
+ apiKeyEnv: QWEN_TOKEN_PLAN_API_KEY_ENV,
42
+ models: QWEN_TOKEN_PLAN_MODELS,
43
+ };
44
+ }
45
+ export function isQwenTokenPlanProvider(provider) {
46
+ return provider === QWEN_TOKEN_PLAN_PROVIDER_ID;
47
+ }
48
+ export function findQwenTokenPlanModel(modelRegistry, model) {
49
+ if (!model?.provider || !model.id)
50
+ return undefined;
51
+ if (!isQwenTokenPlanProvider(model.provider))
52
+ return undefined;
53
+ return modelRegistry.find(model.provider, model.id);
54
+ }
55
+ export function registerQwenTokenPlanProvider(modelRegistry) {
56
+ return registerOpenAiCompatProvider(modelRegistry, buildQwenTokenPlanSpec(), { baseModels: [] });
57
+ }
58
+ export function unregisterQwenTokenPlanProvider(modelRegistry) {
59
+ unregisterOpenAiCompatProvider(modelRegistry, QWEN_TOKEN_PLAN_PROVIDER_ID);
60
+ }
61
+ export function resetQwenTokenPlanProviderRegistration() {
62
+ resetOpenAiCompatProviderRegistration();
63
+ }
64
+ export function getDefaultQwenTokenPlanModels() {
65
+ return QWEN_TOKEN_PLAN_MODELS;
66
+ }
67
+ export { OPENAI_COMPLETIONS_API };
@@ -21,15 +21,15 @@ export function applySingleEventToNodes(nodes, byId, piboSessionId, storedEvent,
21
21
  return;
22
22
  }
23
23
  if (payload.type === "assistant_delta") {
24
- mergeAssistantDeltaEvent(nodes, byId, payload, sessionStatus, storedEvent.createdAt, storedEvent.eventSequence, storedEvent.streamId, storedEvent.streamFrameIndex);
24
+ mergeAssistantDeltaEvent(nodes, byId, payload, sessionStatus, storedEvent.createdAt, storedEvent.eventSequence, storedEvent.streamId, storedEvent.streamFrameIndex, storedEvent.traceSource);
25
25
  return;
26
26
  }
27
27
  if (payload.type === "thinking_delta") {
28
- mergeThinkingDeltaEvent(nodes, byId, payload, sessionStatus, storedEvent.createdAt, storedEvent.eventSequence, storedEvent.streamId, storedEvent.streamFrameIndex);
28
+ mergeThinkingDeltaEvent(nodes, byId, payload, sessionStatus, storedEvent.createdAt, storedEvent.eventSequence, storedEvent.streamId, storedEvent.streamFrameIndex, storedEvent.traceSource);
29
29
  return;
30
30
  }
31
31
  if (payload.type === "assistant_message") {
32
- const node = assistantMessageNodeFromEvent(piboSessionId, payload, storedEvent.createdAt, storedEvent.eventSequence, storedEvent.streamId, storedEvent.streamFrameIndex);
32
+ const node = assistantMessageNodeFromEvent(piboSessionId, payload, storedEvent.createdAt, storedEvent.eventSequence, storedEvent.streamId, storedEvent.streamFrameIndex, storedEvent.traceSource);
33
33
  const existing = byId.get(node.id) ?? findMatchingContentNode(byId, node);
34
34
  if (existing) {
35
35
  mergeAssistantMessageEvent(existing, node);
@@ -39,7 +39,7 @@ export function applySingleEventToNodes(nodes, byId, piboSessionId, storedEvent,
39
39
  byId.set(node.id, node);
40
40
  return;
41
41
  }
42
- const node = traceNodeFromEvent(piboSessionId, payload, childByParent, linkedChildByToolCallId, sessionStatus, storedEvent.createdAt, storedEvent.eventSequence, storedEvent.streamId, storedEvent.streamFrameIndex);
42
+ const node = traceNodeFromEvent(piboSessionId, payload, childByParent, linkedChildByToolCallId, sessionStatus, storedEvent.createdAt, storedEvent.eventSequence, storedEvent.streamId, storedEvent.streamFrameIndex, storedEvent.traceSource);
43
43
  if (!node)
44
44
  return;
45
45
  if (node.type === "user.message" &&
@@ -117,7 +117,7 @@ export function applySingleEventToNodes(nodes, byId, piboSessionId, storedEvent,
117
117
  for (const indexed of flattenTraceNodes([node]))
118
118
  byId.set(indexed.id, indexed);
119
119
  }
120
- function assistantMessageNodeFromEvent(piboSessionId, event, createdAt, eventSequence, streamId, streamFrameIndex) {
120
+ function assistantMessageNodeFromEvent(piboSessionId, event, createdAt, eventSequence, streamId, streamFrameIndex, traceSource) {
121
121
  const eventId = typeof event.eventId === "string" ? event.eventId : undefined;
122
122
  const assistantId = assistantEventNodeId(event);
123
123
  const id = assistantId ? assistantMessageNodeId(assistantId) : `event:${event.type}:${cryptoSafeId(event)}`;
@@ -135,7 +135,7 @@ function assistantMessageNodeFromEvent(piboSessionId, event, createdAt, eventSeq
135
135
  output: event.text,
136
136
  source: "event-log",
137
137
  stableKey: assistantId ? `assistant:${assistantId}` : eventStableKey(event),
138
- orderKey: eventTraceNodeOrder(eventSequence, event.type, streamId, streamFrameIndex),
138
+ orderKey: eventTraceNodeOrder(eventSequence, event.type, streamId, streamFrameIndex, traceSource),
139
139
  children: [],
140
140
  };
141
141
  }
@@ -356,7 +356,7 @@ export function latestTraceStreamId(events, initial) {
356
356
  return latest;
357
357
  }
358
358
  // ── event → node helpers ─────────────────────────────────────────
359
- function traceNodeFromEvent(piboSessionId, event, childByParent, linkedChildByToolCallId, sessionStatus, createdAt, eventSequence, streamId, streamFrameIndex) {
359
+ function traceNodeFromEvent(piboSessionId, event, childByParent, linkedChildByToolCallId, sessionStatus, createdAt, eventSequence, streamId, streamFrameIndex, traceSource) {
360
360
  const eventId = "eventId" in event && typeof event.eventId === "string" ? event.eventId : undefined;
361
361
  const id = `event:${event.type}:${eventId ?? cryptoSafeId(event)}`;
362
362
  const turnParentId = eventId ? messageTurnNodeId(eventId) : undefined;
@@ -367,7 +367,7 @@ function traceNodeFromEvent(piboSessionId, event, childByParent, linkedChildByTo
367
367
  startedAt: createdAt,
368
368
  source: "event-log",
369
369
  stableKey: eventStableKey(event),
370
- orderKey: eventTraceNodeOrder(eventSequence, event.type, streamId, streamFrameIndex),
370
+ orderKey: eventTraceNodeOrder(eventSequence, event.type, streamId, streamFrameIndex, traceSource),
371
371
  children: [],
372
372
  };
373
373
  switch (event.type) {
@@ -382,7 +382,7 @@ function traceNodeFromEvent(piboSessionId, event, childByParent, linkedChildByTo
382
382
  startedAt: createdAt,
383
383
  source: "event-log",
384
384
  stableKey: eventId ? `run-notification:${eventId}` : id,
385
- orderKey: eventTraceNodeOrder(eventSequence, event.type, streamId, streamFrameIndex),
385
+ orderKey: eventTraceNodeOrder(eventSequence, event.type, streamId, streamFrameIndex, traceSource),
386
386
  notification,
387
387
  });
388
388
  }
@@ -555,7 +555,7 @@ function traceNodeFromEvent(piboSessionId, event, childByParent, linkedChildByTo
555
555
  return undefined;
556
556
  }
557
557
  }
558
- function mergeAssistantDeltaEvent(nodes, byId, event, sessionStatus, createdAt, eventSequence, streamId, streamFrameIndex) {
558
+ function mergeAssistantDeltaEvent(nodes, byId, event, sessionStatus, createdAt, eventSequence, streamId, streamFrameIndex, traceSource) {
559
559
  if (typeof event.text !== "string" || event.text.length === 0)
560
560
  return;
561
561
  const assistantId = assistantEventNodeId(event);
@@ -581,7 +581,7 @@ function mergeAssistantDeltaEvent(nodes, byId, event, sessionStatus, createdAt,
581
581
  output: event.text,
582
582
  source: "event-log",
583
583
  stableKey: assistantId ? `assistant:${assistantId}` : id,
584
- orderKey: eventTraceNodeOrder(eventSequence, event.type, streamId, streamFrameIndex),
584
+ orderKey: eventTraceNodeOrder(eventSequence, event.type, streamId, streamFrameIndex, traceSource),
585
585
  children: [],
586
586
  };
587
587
  nodes.push(node);
@@ -599,7 +599,7 @@ function mergeAssistantMessageEvent(target, update) {
599
599
  target.error = update.error ?? target.error;
600
600
  target.completedAt = update.completedAt ?? target.completedAt;
601
601
  }
602
- function mergeThinkingDeltaEvent(nodes, byId, event, sessionStatus, createdAt, eventSequence, streamId, streamFrameIndex) {
602
+ function mergeThinkingDeltaEvent(nodes, byId, event, sessionStatus, createdAt, eventSequence, streamId, streamFrameIndex, traceSource) {
603
603
  if (typeof event.text !== "string" || event.text.length === 0)
604
604
  return;
605
605
  const thinkingId = thinkingEventNodeId(event);
@@ -626,7 +626,7 @@ function mergeThinkingDeltaEvent(nodes, byId, event, sessionStatus, createdAt, e
626
626
  output: event.text,
627
627
  source: "event-log",
628
628
  stableKey,
629
- orderKey: eventTraceNodeOrder(eventSequence, event.type, streamId, streamFrameIndex),
629
+ orderKey: eventTraceNodeOrder(eventSequence, event.type, streamId, streamFrameIndex, traceSource),
630
630
  children: [],
631
631
  };
632
632
  nodes.push(node);
@@ -806,8 +806,8 @@ function isOpenTranscriptEvent(event) {
806
806
  event.type === "thinking_delta" ||
807
807
  event.type === "thinking_finished");
808
808
  }
809
- function eventTraceNodeOrder(eventSequence, type, streamId, streamFrameIndex) {
810
- if (eventSequence === undefined && (streamId !== undefined || streamFrameIndex !== undefined)) {
809
+ function eventTraceNodeOrder(eventSequence, type, streamId, streamFrameIndex, traceSource) {
810
+ if (traceSource === "live") {
811
811
  return liveTraceOrder(streamId, streamFrameIndex, eventNodeKind(type));
812
812
  }
813
813
  return eventTraceOrder(eventSequence, eventNodeKind(type));
@@ -148,6 +148,7 @@ function makeStored(streamEvent, piboSessionId, type, payload, nextSequence, now
148
148
  eventSequence: sequence,
149
149
  streamId: typeof streamEvent.streamId === "number" ? streamEvent.streamId : undefined,
150
150
  streamFrameIndex,
151
+ traceSource: "live",
151
152
  eventId: typeof payload.eventId === "string" ? payload.eventId : undefined,
152
153
  type,
153
154
  createdAt: streamEventCreatedAt(streamEvent) ?? now(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pasko70/pibo",
3
- "version": "1.15.0",
3
+ "version": "1.16.1",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  "packages/workflows",