@starlein/paperclip-plugin-company-wizard 0.4.22 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/worker.js CHANGED
@@ -12900,7 +12900,7 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
12900
12900
  var manifest = {
12901
12901
  id: "starlein.paperclip-plugin-company-wizard",
12902
12902
  apiVersion: 1,
12903
- version: "0.4.22",
12903
+ version: "0.5.0",
12904
12904
  displayName: "Company Wizard",
12905
12905
  description: "AI-powered wizard to bootstrap agent companies from composable templates",
12906
12906
  author: "Sascha Pietrowski <sp@speednetwork.de>",
@@ -12943,11 +12943,26 @@ var manifest = {
12943
12943
  default: "https://github.com/starlein/paperclip-plugin-company-wizard/tree/main/templates",
12944
12944
  description: "GitHub tree URL for template downloads. The default is correct for most setups \u2014 only change this if using a custom fork."
12945
12945
  },
12946
- anthropicApiKey: {
12946
+ aiProvider: {
12947
12947
  type: "string",
12948
+ enum: ["anthropic", "openai"],
12949
+ default: "anthropic",
12950
+ description: "AI provider used for company generation. Anthropic uses Claude Opus 5 at max effort; OpenAI uses GPT-5.6 Sol at high reasoning effort."
12951
+ },
12952
+ anthropicApiKey: {
12953
+ // Paperclip's secret picker submits an EnvSecretRefBinding object,
12954
+ // while direct keys and older hosts still submit strings. Keep both
12955
+ // representations schema-valid; the host validates governed bindings
12956
+ // and the worker resolves them before calling Anthropic.
12957
+ type: ["string", "object"],
12948
12958
  format: "secret-ref",
12949
12959
  description: "Anthropic API key for the AI wizard. Paste a key or select a saved Paperclip company secret; Paperclip stores pasted values as governed secret references."
12950
12960
  },
12961
+ openaiApiKey: {
12962
+ type: ["string", "object"],
12963
+ format: "secret-ref",
12964
+ description: "OpenAI API key for GPT/Codex company generation. Paste a key or select a saved Paperclip company secret; required when AI Provider is openai."
12965
+ },
12951
12966
  paperclipUrl: {
12952
12967
  type: "string",
12953
12968
  description: "Paperclip instance URL. Defaults to http://localhost:3100 or the PAPERCLIP_PUBLIC_URL env var."
@@ -15000,9 +15015,14 @@ function refreshTemplatesCache(cfg, log) {
15000
15015
  log?.(`\u2713 Refreshed templates cache from ${repoUrl} \u2192 ${targetDir}`);
15001
15016
  return targetDir;
15002
15017
  }
15018
+ var ANTHROPIC_AI_MODEL = "claude-opus-5";
15019
+ var OPENAI_AI_MODEL = "gpt-5.6-sol";
15003
15020
  function isLikelyAnthropicApiKey(value) {
15004
15021
  return value.startsWith("sk-ant-");
15005
15022
  }
15023
+ function isLikelyOpenAiApiKey(value) {
15024
+ return value.startsWith("sk-");
15025
+ }
15006
15026
  async function resolveAnthropicApiKey(ctx, configuredValue, companyId) {
15007
15027
  const options = {
15008
15028
  ...companyId ? { companyId } : {},
@@ -15025,6 +15045,28 @@ async function resolveAnthropicApiKey(ctx, configuredValue, companyId) {
15025
15045
  );
15026
15046
  }
15027
15047
  }
15048
+ async function resolveOpenAiApiKey(ctx, configuredValue, companyId) {
15049
+ const options = {
15050
+ ...companyId ? { companyId } : {},
15051
+ configPath: "openaiApiKey"
15052
+ };
15053
+ if (configuredValue && typeof configuredValue === "object" && configuredValue.type === "secret_ref" && typeof configuredValue.secretId === "string") {
15054
+ return ctx.secrets.resolve(configuredValue, options);
15055
+ }
15056
+ if (typeof configuredValue !== "string") return "";
15057
+ const value = configuredValue.trim();
15058
+ if (!value) return "";
15059
+ if (isLikelyOpenAiApiKey(value)) return value;
15060
+ try {
15061
+ const resolved = await ctx.secrets.resolve(value, options);
15062
+ return resolved.trim();
15063
+ } catch (err) {
15064
+ const detail = err instanceof Error ? err.message : String(err);
15065
+ throw new Error(
15066
+ `OpenAI API key could not be resolved. Re-save the plugin setting with a valid OpenAI API key. ${detail}`
15067
+ );
15068
+ }
15069
+ }
15028
15070
  async function callAnthropic(apiKey, system, messages) {
15029
15071
  try {
15030
15072
  const response = await fetch("https://api.anthropic.com/v1/messages", {
@@ -15035,11 +15077,13 @@ async function callAnthropic(apiKey, system, messages) {
15035
15077
  "anthropic-version": "2023-06-01"
15036
15078
  },
15037
15079
  body: JSON.stringify({
15038
- model: "claude-opus-4-8",
15080
+ model: ANTHROPIC_AI_MODEL,
15039
15081
  // High ceiling so the wizard's final config — which reproduces the user's
15040
15082
  // full spec in the first goal — is never truncated mid-JSON. This is only
15041
15083
  // an upper bound; generation time tracks the tokens actually produced.
15042
- max_tokens: 32768,
15084
+ max_tokens: 65536,
15085
+ thinking: { type: "adaptive" },
15086
+ output_config: { effort: "max" },
15043
15087
  ...system ? { system } : {},
15044
15088
  messages
15045
15089
  })
@@ -15049,7 +15093,53 @@ async function callAnthropic(apiKey, system, messages) {
15049
15093
  return { text: "", error: `Anthropic API error (${response.status}): ${body}` };
15050
15094
  }
15051
15095
  const data = await response.json();
15052
- return { text: data.content?.[0]?.text || "" };
15096
+ if (data.stop_reason && data.stop_reason !== "end_turn" && data.stop_reason !== "stop_sequence") {
15097
+ return {
15098
+ text: "",
15099
+ error: `Anthropic generation stopped before completion (${data.stop_reason}).`
15100
+ };
15101
+ }
15102
+ const text = data.content?.filter((block) => block.type === "text" && typeof block.text === "string").map((block) => block.text).join("") || "";
15103
+ return text ? { text } : { text: "", error: "Anthropic returned no text output." };
15104
+ } catch (err) {
15105
+ return { text: "", error: err instanceof Error ? err.message : String(err) };
15106
+ }
15107
+ }
15108
+ async function callOpenAi(apiKey, system, messages) {
15109
+ try {
15110
+ const input = Array.isArray(messages) ? messages.map((message) => {
15111
+ const record = message;
15112
+ return { role: record.role, content: record.content };
15113
+ }) : messages;
15114
+ const response = await fetch("https://api.openai.com/v1/responses", {
15115
+ method: "POST",
15116
+ headers: {
15117
+ "Content-Type": "application/json",
15118
+ Authorization: `Bearer ${apiKey}`
15119
+ },
15120
+ body: JSON.stringify({
15121
+ model: OPENAI_AI_MODEL,
15122
+ reasoning: { effort: "high" },
15123
+ max_output_tokens: 65536,
15124
+ ...typeof system === "string" && system ? { instructions: system } : {},
15125
+ input
15126
+ })
15127
+ });
15128
+ if (!response.ok) {
15129
+ const body = await response.text();
15130
+ return { text: "", error: `OpenAI API error (${response.status}): ${body}` };
15131
+ }
15132
+ const data = await response.json();
15133
+ if (data.status && data.status !== "completed") {
15134
+ const reason = data.incomplete_details?.reason || data.status;
15135
+ return { text: "", error: `OpenAI generation stopped before completion (${reason}).` };
15136
+ }
15137
+ const content = data.output?.flatMap((item) => item.content ?? []) ?? [];
15138
+ if (content.some((part) => part.type === "refusal")) {
15139
+ return { text: "", error: "OpenAI refused the generation request." };
15140
+ }
15141
+ const text = data.output_text || content.filter((part) => part.type === "output_text" && typeof part.text === "string").map((part) => part.text).join("");
15142
+ return text ? { text } : { text: "", error: "OpenAI returned no text output." };
15053
15143
  } catch (err) {
15054
15144
  return { text: "", error: err instanceof Error ? err.message : String(err) };
15055
15145
  }
@@ -15907,18 +15997,20 @@ var plugin = definePlugin({
15907
15997
  }
15908
15998
  const cfg = await ctx.config.get() ?? {};
15909
15999
  const companyId = typeof params.companyId === "string" ? params.companyId : void 0;
15910
- const apiKey = await resolveAnthropicApiKey(ctx, cfg.anthropicApiKey, companyId);
16000
+ const provider = cfg.aiProvider === "openai" ? "openai" : "anthropic";
16001
+ const apiKey = provider === "openai" ? await resolveOpenAiApiKey(ctx, cfg.openaiApiKey, companyId) : await resolveAnthropicApiKey(ctx, cfg.anthropicApiKey, companyId);
15911
16002
  if (!apiKey) {
15912
16003
  return {
15913
16004
  text: "",
15914
- error: "Anthropic API key not configured. Add it in plugin settings (anthropicApiKey)."
16005
+ error: provider === "openai" ? "OpenAI API key not configured. Add it in plugin settings (openaiApiKey)." : "Anthropic API key not configured. Add it in plugin settings (anthropicApiKey)."
15915
16006
  };
15916
16007
  }
16008
+ const generate = () => provider === "openai" ? callOpenAi(apiKey, params.system, params.messages) : callAnthropic(apiKey, params.system, params.messages);
15917
16009
  if (params.mode === "start") {
15918
16010
  sweepAiChatJobs();
15919
16011
  const jobId = `aichat-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
15920
16012
  aiChatJobs.set(jobId, { status: "pending", createdAt: Date.now() });
15921
- void callAnthropic(apiKey, params.system, params.messages).then((result) => {
16013
+ void generate().then((result) => {
15922
16014
  aiChatJobs.set(
15923
16015
  jobId,
15924
16016
  result.error ? { status: "error", error: result.error, createdAt: Date.now() } : { status: "done", text: result.text, createdAt: Date.now() }
@@ -15926,7 +16018,7 @@ var plugin = definePlugin({
15926
16018
  });
15927
16019
  return { jobId, status: "pending" };
15928
16020
  }
15929
- return await callAnthropic(apiKey, params.system, params.messages);
16021
+ return await generate();
15930
16022
  } catch (err) {
15931
16023
  return { text: "", error: err instanceof Error ? err.message : String(err) };
15932
16024
  }
@@ -15935,14 +16027,19 @@ var plugin = definePlugin({
15935
16027
  try {
15936
16028
  const cfg = await ctx.config.get() ?? {};
15937
16029
  const companyId = typeof params.companyId === "string" ? params.companyId : void 0;
15938
- const apiKey = await resolveAnthropicApiKey(ctx, cfg.anthropicApiKey, companyId);
16030
+ const provider = cfg.aiProvider === "openai" ? "openai" : "anthropic";
16031
+ const apiKey = provider === "openai" ? await resolveOpenAiApiKey(ctx, cfg.openaiApiKey, companyId) : await resolveAnthropicApiKey(ctx, cfg.anthropicApiKey, companyId);
15939
16032
  if (!apiKey) {
15940
16033
  return {
15941
16034
  ok: false,
15942
- error: "Anthropic API key not configured. Add it in plugin settings (anthropicApiKey)."
16035
+ error: provider === "openai" ? "OpenAI API key not configured. Add it in plugin settings (openaiApiKey)." : "Anthropic API key not configured. Add it in plugin settings (anthropicApiKey)."
15943
16036
  };
15944
16037
  }
15945
- return { ok: true };
16038
+ return {
16039
+ ok: true,
16040
+ provider,
16041
+ model: provider === "openai" ? OPENAI_AI_MODEL : ANTHROPIC_AI_MODEL
16042
+ };
15946
16043
  } catch (err) {
15947
16044
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
15948
16045
  }